diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..040cf24e --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Gradle template +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# We don't need those .rej files +*.rej + +### Kettle +/projects/ +/bin/ + +### IDEs +/.idea/ +.classpath +.project +/.settings/ +classes/ + +run/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..bbf0343e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,42 @@ +image: openjdk:8-jdk + +stages: +- setup +- test +- build + +before_script: +# - echo `pwd` # debug +# - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug +- export GRADLE_USER_HOME=`pwd`/.gradle +- git submodule sync --recursive +- git submodule update --init --recursive + +cache: + paths: + - .gradle/wrapper + - .gradle/caches + +setup: + stage: setup + script: + - chmod +x gradlew && ./gradlew setup + - mv src/main/java/org* projects/Kettle/src/main/java + +test: + stage: test + script: + - chmod +x gradlew && ./gradlew check + +build: + stage: build + script: + - chmod +x gradlew && ./gradlew build githubRelease + only: + - master + artifacts: + paths: + - build/distributions/*.jar + +after_script: +- echo "End CI" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..12f3481f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "forge"] + path = forge + url = https://github.com/MinecraftForge/MinecraftForge.git + branch = 1.12.x diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3b1aa60e --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Kettle + Copyright (C) 2019 Kettle Foundation + + 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 . + +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: + + Kettle Copyright (C) 2019 Kettle Foundation + 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 +. + + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..fff6b4b9 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +![Magma](https://img.hexeption.co.uk/magma-new.png) + +# Magma + +![](https://img.shields.io/badge/Minecraft%20Forge-1.12.2%20--%202838-orange.svg?style=for-the-badge) [![](https://img.shields.io/jenkins/build/https/ci.hexeption.co.uk/job/Magma?label=CI&style=for-the-badge)](https://ci.hexeption.co.uk) + +### What's Magma? + +Magma next generation in hybrid servers. + +Kettle is based on Forge and Paper builds, meaning it can run both Craftbukkit/Spigot/Paper-based plugins and forge-based mods. + +We hope to eliminate all issues with craftbukkit forge servers. In the end, we envision a seamless, low lag Kettle experience with support for new 1.12+ versions of Minecraft. + +## Deployment + +### Installation + +1. Download the recommended builds from the [**Releases** section](https://github.com/KettleFoundation/Kettle/releases) + 1. Download Beta builds from the [**CI**](https://ci.hexeption.co.uk/job/Magma/) +2. Make a new folder for the server +3. Move the jar to the folder +4. Run the jar + +### Building the sources + +All builds are available in `build/distributions` + +- Clone the Project + - You can use GitHub Desktop/GitKraken or clone using the terminal + - `git clone https://github.com/KettleFoundation/Kettle` + - Next you are gonna want to clone the submodule + - `git submodule update --init --recursive` +- Building + - First you want to setup the project + - `./gradlew setup` + - After you have setup the project you are going to want to run the build + - `./gradlew build` + - Now go and get a drink this may take some time + +## Chat + +You are welcome to visit Kettle Discord server [here](https://discord.gg/RqDjbcM). + +## Unstable/Test builds + +For unstable/test builds you can check the [__CI__](https://ci.hexeption.co.uk/job/Magma) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..d25e35cc --- /dev/null +++ b/build.gradle @@ -0,0 +1,253 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + maven { + name = "forge" + url = "https://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + maven { + url "https://jcenter.bintray.com" + } + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.3+' + classpath 'net.nemerosa:versioning:2.7.1' + classpath "gradle.plugin.com.github.breadmoirai:github-release:2.2.4" + } +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: "idea" +apply plugin: 'net.minecraftforge.gradle.patcher' +apply plugin: 'net.minecraftforge.gradle.launch4j' +apply plugin: 'net.nemerosa.versioning' +apply plugin: "com.github.breadmoirai.github-release" + +group 'kettlefoundation' +version 'git-' + versioning.info.branch + "-" + versioning.info.build + +repositories { + clear() + mavenCentral() + maven { + name = "forge" + url = "https://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + maven { + name 'minecraft' + url 'https://libraries.minecraft.net/' + } + maven { + name 'maven' + url 'https://mvnrepository.com/artifact/' + } + maven { + name 'KettleFoundation' + url 'https://repo.hexeption.co.uk/artifactory/Kettle/' + } + flatDir { + dirs 'src/main/resources' + } +} + +configurations{ + compile.extendsFrom exported + libraries +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile(group: 'bukkit', name: 'bukkit', version: '1.12.2-R0.1') +} + +jar{ + into('lib') { + from configurations.libraries + } + exclude "**/configurations/**" + exclude "**/META-INF/**" + exclude "log4j2.xml" +} + +minecraft { + version = "1.12.2" + mappings = "snapshot_nodoc_20171003" + workspaceDir = "projects" + versionJson = "jsons/1.12.2-dev.json" + buildUserdev = true + buildInstaller = false + installerVersion = "1.5" + + def common = { + patchPrefixOriginal "../src-base/minecraft" + patchPrefixChanged "../src-work/minecraft" + mainClassServer "net.minecraft.launchwrapper.Launch" + tweakClassServer "net.minecraftforge.fml.common.launcher.FMLServerTweaker" + } + + projects { + forge { + rootDir "forge/" + patchDir "forge/patches/minecraft/" + patchAfter "clean" + genPatchesFrom "clean" + genMcpPatches = false + applyMcpPatches = false + s2sKeepImports = true + with common + } + + kettle { + rootDir "." + patchDir "patches/" + patchAfter "forge" + genPatchesFrom "forge" + genMcpPatches = true + applyMcpPatches = true + s2sKeepImports = true + with common + } + } +} + +sourceCompatibility = 1.8 +tasks.generateXmlConfig.dependsOn installer +tasks.compileJava.enabled = false +tasks.build.dependsOn 'launch4j' +tasks.reobfuscate.setProperty("extraSrg",["PK: org/bukkit/craftbukkit org/bukkit/craftbukkit/v1_12_R1"]) + +installer { + classifier = 'installer' + from "forge/src/main/resources/forge_logo.png" + rename "forge_logo\\.png", "big_logo.png" +} + +launch4j { + jar = installer.archivePath.canonicalPath + outfile = file("build/distributions/${project.name}-${project.version}-installer-win.exe").canonicalPath + icon = file('icon.ico').canonicalPath + manifest = file('l4jManifest.xml').canonicalPath + jreMinVersion = '1.8.0' + initialHeapPercent = 5 + maxHeapPercent = 100 +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +task signUniversal(type: SignJar, dependsOn: 'outputJar') { + onlyIf { + project.hasProperty('jarsigner') + } + + def jarsigner = [:] + + if (project.hasProperty('jarsigner')) + jarsigner = project.jarsigner + + alias = 'forge' + exclude "paulscode/**" + storePass = jarsigner.storepass + keyPass = jarsigner.keypass + keyStore = jarsigner.keystore + inputFile = outputJar.archivePath + outputFile = outputJar.archivePath +} +build.dependsOn signUniversal +installer.dependsOn signUniversal + +outputJar { + classifier = 'universal' + + manifest.attributes([ + "Implementation-Title": "Kettle", + "Implementation-Version": version, + "Main-Class": "net.minecraftforge.fml.relauncher.ServerLaunchWrapper", + "TweakClass": "net.minecraftforge.fml.common.launcher.FMLTweaker", + "Class-Path": getServerClasspath(file("jsons/1.12.2-rel.json")) + ]) +} + +processJson { + releaseJson = "jsons/1.12.2-rel.json" + addReplacements([ + "@minecraft_version@": project.minecraft.version, + "@version@": project.version, + "@project@": "kettle", + "@artifact@": "kf:kettle:${project.version}", + "@universal_jar@": { outputJar.archiveName }, + "@timestamp@": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + ]) +} + +extractForgeSources { + exclude "**/SideOnly.java", "**/Side.java" +} + +genGradleProjects { + addCompileDep "junit:junit:4.12" + addCompileDep "bukkit:bukkit:1.12.2-R0.1" + addRepo("KettleFoundation", "https://repo.hexeption.co.uk/artifactory/Kettle/") +} + +import groovy.json.JsonSlurper + +import java.util.stream.Collectors + +String getServerClasspath(File file) { + def node = new JsonSlurper().parse(file) + def out = new StringBuilder() + node.versionInfo.libraries.each { lib -> + if (lib.serverreq) + { + def split = lib.name.split(':') + def group = split[0].replace('.', '/') + def artifact = split[1] + def version = split[2] + out += "libraries/$group/$artifact/$version/$artifact-${version}.jar " + } + } + out += "minecraft_server.1.12.2.jar" + return out.toString() +} + +githubRelease { + if(System.getenv("GITHUB_TOKEN") != null){ + token System.getenv("GITHUB_TOKEN") + }else{ + token "" + } + owner "KettleFoundation" + repo "Kettle" + releaseName "Kettle Dev " + versioning.info.branch + "-" + versioning.info.build + body { + """\ +## Setup Info +Don't forget to download [Minecraft Server 1.12.2](https://launcher.mojang.com/mc/game/1.12.2/server/886945bfb2b978778c3a0288fd7fab09d315b25f/server.jar) + +(Rename it to `minecraft_server.1.12.2.jar`) + +${(changelog().call().readLines().stream().map { "- $it" }.collect(Collectors.joining('\n', '## Changelog\n', '')))} + """ + } + prerelease true + draft true + releaseAssets = [ + fileTree("build/distributions/").filter { it.name.endsWith("jar") || it.name.endsWith("zip") }, + fileTree("/release/").filter { it.name.endsWith("zip") } + ] +} \ No newline at end of file diff --git a/forge b/forge new file mode 160000 index 00000000..8c046513 --- /dev/null +++ b/forge @@ -0,0 +1 @@ +Subproject commit 8c04651301d86edc02ac10a15af243869c87ac51 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..d2f86115 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..1948b907 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0de4674c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Feb 12 08:55:53 GMT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jsons/1.12.2-dev.json b/jsons/1.12.2-dev.json new file mode 100644 index 00000000..0bbda81c --- /dev/null +++ b/jsons/1.12.2-dev.json @@ -0,0 +1,147 @@ +{ + "id": "@minecraft_version@-@project@@version@", + "time": "@timestamp@", + "releaseTime": "1960-01-01T00:00:00-0700", + "type": "release", + "inheritsFrom": "1.12.2", + "minecraftArguments": "--version FML_DEV --tweakClass net.minecraftforge.fml.common.launcher.FMLTweaker", + "libraries": [ + { + "name": "net.minecraft:launchwrapper:1.12" + }, + { + "name": "org.jline:jline:3.5.1", + "children": ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "jline:jline:2.13", + "children": ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "com.google.code.findbugs:jsr305:1.3.9", + "children": ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.ow2.asm:asm-debug-all:5.2", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "com.typesafe.akka:akka-actor_2.11:2.3.3", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "com.typesafe:config:1.2.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang:scala-actors-migration_2.11:1.1.0", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang:scala-compiler:2.11.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang:scala-library:2.11.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang.modules:scala-parser-combinators_2.11:1.0.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang:scala-reflect:2.11.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang.modules:scala-swing_2.11:1.0.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "org.scala-lang.modules:scala-xml_2.11:1.0.2", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name" : "com.googlecode.json-simple:json-simple:1.1.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name" : "org.yaml:snakeyaml:1.23", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "lzma:lzma:0.0.1" + }, + { + "name": "java3d:vecmath:1.5.2" + }, + { + "name": "net.sf.trove4j:trove4j:3.0.3" + }, + { + "name": "org.xerial:sqlite-jdbc:3.21.0.1", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "mysql:mysql-connector-java:5.1.46", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "net.md-5:SpecialSource:1.8.5", + "children" : ["sources"], + "url" : "https://hub.spigotmc.org/nexus/content/groups/public/" + }, + { + "name": "net.md-5:bungeecord-chat:1.12-SNAPSHOT", + "children" : ["sources"], + "url" : "https://hub.spigotmc.org/nexus/content/groups/public/" + }, + { + "name": "org.apache.commons:commons-lang3:3.5", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "commons-lang:commons-lang:2.6", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + }, + { + "name": "co.aikar:fastutil-lite:1.0", + "children" : ["sources"], + "url" : "https://papermc.io/repo/repository/maven-public/" + }, + { + "name": "org.apache.maven:maven-artifact:3.5.3", + "children" : ["sources"], + "url" : "http://repo.maven.apache.org/maven2" + } + ] +} diff --git a/jsons/1.12.2-rel.json b/jsons/1.12.2-rel.json new file mode 100644 index 00000000..426195d0 --- /dev/null +++ b/jsons/1.12.2-rel.json @@ -0,0 +1,217 @@ +{ +"install": { + "profileName": "@project@", + "target":"@minecraft_version@-@project@@version@", + "path":"@artifact@", + "version":"@project@ @version@", + "filePath":"@universal_jar@", + "welcome":"Welcome to the simple @project@ installer.", + "minecraft":"@minecraft_version@", + "mirrorList" : "http://files.minecraftforge.net/mirror-brand.list", + "logo":"/big_logo.png", + "modList":"none" +}, +"versionInfo": { + "id": "@minecraft_version@-@project@@version@", + "time": "@timestamp@", + "releaseTime": "1960-01-01T00:00:00-0700", + "type": "release", + "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --tweakClass net.minecraftforge.fml.common.launcher.FMLTweaker --versionType Forge", + "mainClass": "net.minecraft.launchwrapper.Launch", + "inheritsFrom": "1.12.2", + "jar": "1.12.2", + "logging": {}, + "libraries": [ + { + "name": "@artifact@", + "url": "http://files.minecraftforge.net/maven/" + }, + { + "name": "net.minecraft:launchwrapper:1.12", + "serverreq":true + }, + { + "name": "org.ow2.asm:asm-all:5.2", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "2ea49e08b876bbd33e0a7ce75c8f371d29e1f10a" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.jline:jline:3.5.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "51800e9d7a13608894a5a28eed0f5c7fa2f300fb" ], + "serverreq":true, + "clientreq":false + }, + { + "name": "net.java.dev.jna:jna:4.4.0", + "serverreq":true, + "clientreq":false + }, + { + "name": "com.typesafe.akka:akka-actor_2.11:2.3.3", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "ed62e9fc709ca0f2ff1a3220daa8b70a2870078e", "25a86ccfdb6f6dfe08971f4825d0a01be83a6f2e" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "com.typesafe:config:1.2.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "f771f71fdae3df231bcd54d5ca2d57f0bf93f467", "7d7bc36df0989d72f2d5d057309675777acc528b" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-actors-migration_2.11:1.1.0", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "dfa8bc42b181d5b9f1a5dd147f8ae308b893eb6f", "8c9aaeeb68487ca519411a14068e1b4d69739207" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-compiler:2.11.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "56ea2e6c025e0821f28d73ca271218b8dd04926a", "1444992390544ba3780867a13ff696a89d7d1639" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "87213338cd5a153a7712cb574c0ddd2edfee0386", "0b4c1bf8d48993f138d6e10c0c144e50acfff581" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "1f7371605d4ba42aa26d3443440c0083c587b4e9", "1ea655dda4504ae0a367327e2340cd3beaee6c73" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-library:2.11.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "0e11da23da3eabab9f4777b9220e60d44c1aab6a", "1e4df76e835201c6eabd43adca89ab11f225f134" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-parser-combinators_2.11:1.0.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "f05d7345bf5a58924f2837c6c1f4d73a938e1ff0", "a1cbbcbde1dcc614f4253ed1aa0b320bc78d8f1d" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-reflect:2.11.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "6580347e61cc7f8e802941e7fde40fa83b8badeb", "91ce0f0be20f4a536321724b4b3bbc6530ddcd88" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-swing_2.11:1.0.1", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "b1cdd92bd47b1e1837139c1c53020e86bb9112ae", "d77152691dcf5bbdb00529af37aa7d3d887b3e63" ], + "serverreq":true, + "clientreq":true + }, + { + "name": "org.scala-lang:scala-xml_2.11:1.0.2", + "url" : "http://files.minecraftforge.net/maven/", + "checksums" : [ "7a80ec00aec122fba7cd4e0d4cdd87ff7e4cb6d0", "62736b01689d56b6d09a0164b7ef9da2b0b9633d" ], + "serverreq":true, + "clientreq":true + }, + { + "name" : "com.googlecode.json-simple:json-simple:1.1.1", + "url" : "http://repo.maven.apache.org/maven2", + "serverreq":true, + "clientreq":true + }, + { + "name" : "org.yaml:snakeyaml:1.23", + "url" : "http://repo.maven.apache.org/maven2", + "serverreq":true, + "clientreq":true + }, + { + "name": "lzma:lzma:0.0.1", + "serverreq":true + }, + { + "name": "net.sf.jopt-simple:jopt-simple:5.0.3", + "serverreq":true + }, + { + "name": "java3d:vecmath:1.5.2", + "clientreq":true, + "serverreq":true + }, + { + "name": "net.sf.trove4j:trove4j:3.0.3", + "clientreq":true, + "serverreq":true + }, + { + "name": "mysql:mysql-connector-java:5.1.46", + "url" : "http://repo.maven.apache.org/maven2", + "clientreq":true, + "serverreq":true + }, + { + "name": "org.xerial:sqlite-jdbc:3.21.0.1", + "url" : "http://repo.maven.apache.org/maven2", + "clientreq":true, + "serverreq":true + }, + { + "name": "org.apache.commons:commons-lang3:3.5", + "url" : "http://repo.maven.apache.org/maven2", + "clientreq":true, + "serverreq":true + }, + { + "name": "commons-lang:commons-lang:2.6", + "url" : "http://repo.maven.apache.org/maven2", + "clientreq":true, + "serverreq":true + }, + { + "name": "net.md-5:SpecialSource:1.8.5", + "url" : "https://hub.spigotmc.org/nexus/content/groups/public/", + "clientreq":true, + "serverreq":true + }, + { + "name": "jline:jline:2.13", + "url" : "http://repo.maven.apache.org/maven2", + "clientreq":true, + "serverreq":true + }, + { + "name": "net.md-5:bungeecord-chat:1.12-SNAPSHOT", + "url" : "https://hub.spigotmc.org/nexus/content/groups/public/", + "clientreq":true, + "serverreq":true + }, + { + "name": "co.aikar:fastutil-lite:1.0", + "url" : "https://papermc.io/repo/repository/maven-public/", + "clientreq":true, + "serverreq":true + }, + { + "name": "org.apache.maven:maven-artifact:3.5.3", + "url" : "http://files.minecraftforge.net/maven/", + "clientreq":true, + "serverreq":true + } + ] +}, +"optionals": [ +] +} diff --git a/jsons/1.12.2.json b/jsons/1.12.2.json new file mode 100644 index 00000000..dbe4d3f4 --- /dev/null +++ b/jsons/1.12.2.json @@ -0,0 +1 @@ +{"assetIndex": {"id": "1.12", "sha1": "1584b57c1a0b5e593fad1f5b8f78536ca640547b", "size": 143138, "totalSize": 129336389, "url": "https://launchermeta.mojang.com/v1/packages/1584b57c1a0b5e593fad1f5b8f78536ca640547b/1.12.json"}, "assets": "1.12", "downloads": {"client": {"sha1": "0f275bc1547d01fa5f56ba34bdc87d981ee12daf", "size": 10180113, "url": "https://launcher.mojang.com/v1/objects/0f275bc1547d01fa5f56ba34bdc87d981ee12daf/client.jar"}, "server": {"sha1": "886945bfb2b978778c3a0288fd7fab09d315b25f", "size": 30222121, "url": "https://launcher.mojang.com/v1/objects/886945bfb2b978778c3a0288fd7fab09d315b25f/server.jar"}}, "id": "1.12.2", "libraries": [{"downloads": {"artifact": {"path": "com/mojang/patchy/1.1/patchy-1.1.jar", "sha1": "aef610b34a1be37fa851825f12372b78424d8903", "size": 15817, "url": "https://libraries.minecraft.net/com/mojang/patchy/1.1/patchy-1.1.jar"}}, "name": "com.mojang:patchy:1.1"}, {"downloads": {"artifact": {"path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar", "sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8", "size": 30973, "url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar"}}, "name": "oshi-project:oshi-core:1.1"}, {"downloads": {"artifact": {"path": "net/java/dev/jna/jna/4.4.0/jna-4.4.0.jar", "sha1": "cb208278274bf12ebdb56c61bd7407e6f774d65a", "size": 1091208, "url": "https://libraries.minecraft.net/net/java/dev/jna/jna/4.4.0/jna-4.4.0.jar"}}, "name": "net.java.dev.jna:jna:4.4.0"}, {"downloads": {"artifact": {"path": "net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar", "sha1": "e3f70017be8100d3d6923f50b3d2ee17714e9c13", "size": 913436, "url": "https://libraries.minecraft.net/net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar"}}, "name": "net.java.dev.jna:platform:3.4.0"}, {"downloads": {"artifact": {"path": "com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar", "sha1": "63d216a9311cca6be337c1e458e587f99d382b84", "size": 1634692, "url": "https://libraries.minecraft.net/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar"}}, "name": "com.ibm.icu:icu4j-core-mojang:51.2"}, {"downloads": {"artifact": {"path": "net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar", "sha1": "cdd846cfc4e0f7eefafc02c0f5dce32b9303aa2a", "size": 78175, "url": "https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar"}}, "name": "net.sf.jopt-simple:jopt-simple:5.0.3"}, {"downloads": {"artifact": {"path": "com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar", "sha1": "c73b5636faf089d9f00e8732a829577de25237ee", "size": 103871, "url": "https://libraries.minecraft.net/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar"}}, "name": "com.paulscode:codecjorbis:20101023"}, {"downloads": {"artifact": {"path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", "size": 5618, "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"}}, "name": "com.paulscode:codecwav:20101023"}, {"downloads": {"artifact": {"path": "com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar", "sha1": "5c5e304366f75f9eaa2e8cca546a1fb6109348b3", "size": 21679, "url": "https://libraries.minecraft.net/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar"}}, "name": "com.paulscode:libraryjavasound:20101123"}, {"downloads": {"artifact": {"path": "com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar", "sha1": "73e80d0794c39665aec3f62eee88ca91676674ef", "size": 18981, "url": "https://libraries.minecraft.net/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar"}}, "name": "com.paulscode:librarylwjglopenal:20100824"}, {"downloads": {"artifact": {"path": "com/paulscode/soundsystem/20120107/soundsystem-20120107.jar", "sha1": "419c05fe9be71f792b2d76cfc9b67f1ed0fec7f6", "size": 65020, "url": "https://libraries.minecraft.net/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar"}}, "name": "com.paulscode:soundsystem:20120107"}, {"downloads": {"artifact": {"path": "io/netty/netty-all/4.1.9.Final/netty-all-4.1.9.Final.jar", "sha1": "0097860965d6a0a6b98e7f569f3f966727b8db75", "size": 3511093, "url": "https://libraries.minecraft.net/io/netty/netty-all/4.1.9.Final/netty-all-4.1.9.Final.jar"}}, "name": "io.netty:netty-all:4.1.9.Final"}, {"downloads": {"artifact": {"path": "com/google/guava/guava/21.0/guava-21.0.jar", "sha1": "3a3d111be1be1b745edfa7d91678a12d7ed38709", "size": 2521113, "url": "https://libraries.minecraft.net/com/google/guava/guava/21.0/guava-21.0.jar"}}, "name": "com.google.guava:guava:21.0"}, {"downloads": {"artifact": {"path": "org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar", "sha1": "6c6c702c89bfff3cd9e80b04d668c5e190d588c6", "size": 479881, "url": "https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar"}}, "name": "org.apache.commons:commons-lang3:3.5"}, {"downloads": {"artifact": {"path": "commons-io/commons-io/2.5/commons-io-2.5.jar", "sha1": "2852e6e05fbb95076fc091f6d1780f1f8fe35e0f", "size": 208700, "url": "https://libraries.minecraft.net/commons-io/commons-io/2.5/commons-io-2.5.jar"}}, "name": "commons-io:commons-io:2.5"}, {"downloads": {"artifact": {"path": "commons-codec/commons-codec/1.10/commons-codec-1.10.jar", "sha1": "4b95f4897fa13f2cd904aee711aeafc0c5295cd8", "size": 284184, "url": "https://libraries.minecraft.net/commons-codec/commons-codec/1.10/commons-codec-1.10.jar"}}, "name": "commons-codec:commons-codec:1.10"}, {"downloads": {"artifact": {"path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar", "sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4", "size": 208338, "url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar"}}, "name": "net.java.jinput:jinput:2.0.5"}, {"downloads": {"artifact": {"path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar", "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6", "size": 7508, "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar"}}, "name": "net.java.jutils:jutils:1.0.0"}, {"downloads": {"artifact": {"path": "com/google/code/gson/gson/2.8.0/gson-2.8.0.jar", "sha1": "c4ba5371a29ac9b2ad6129b1d39ea38750043eff", "size": 231952, "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar"}}, "name": "com.google.code.gson:gson:2.8.0"}, {"downloads": {"artifact": {"path": "com/mojang/authlib/1.5.25/authlib-1.5.25.jar", "sha1": "9834cdf236c22e84b946bba989e2f94ef5897c3c", "size": 65621, "url": "https://libraries.minecraft.net/com/mojang/authlib/1.5.25/authlib-1.5.25.jar"}}, "name": "com.mojang:authlib:1.5.25"}, {"downloads": {"artifact": {"path": "com/mojang/realms/1.10.22/realms-1.10.22.jar", "sha1": "bd0dccebdf3744c75f1ca20063f16e8f7d5e663f", "size": 7135057, "url": "https://libraries.minecraft.net/com/mojang/realms/1.10.22/realms-1.10.22.jar"}}, "name": "com.mojang:realms:1.10.22"}, {"downloads": {"artifact": {"path": "org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar", "sha1": "a698750c16740fd5b3871425f4cb3bbaa87f529d", "size": 365552, "url": "https://libraries.minecraft.net/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar"}}, "name": "org.apache.commons:commons-compress:1.8.1"}, {"downloads": {"artifact": {"path": "org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar", "sha1": "18f4247ff4572a074444572cee34647c43e7c9c7", "size": 589512, "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar"}}, "name": "org.apache.httpcomponents:httpclient:4.3.3"}, {"downloads": {"artifact": {"path": "commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar", "sha1": "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f", "size": 62050, "url": "https://libraries.minecraft.net/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar"}}, "name": "commons-logging:commons-logging:1.1.3"}, {"downloads": {"artifact": {"path": "org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar", "sha1": "31fbbff1ddbf98f3aa7377c94d33b0447c646b6e", "size": 282269, "url": "https://libraries.minecraft.net/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar"}}, "name": "org.apache.httpcomponents:httpcore:4.3.2"}, {"downloads": {"artifact": {"path": "it/unimi/dsi/fastutil/7.1.0/fastutil-7.1.0.jar", "sha1": "9835253257524c1be7ab50c057aa2d418fb72082", "size": 17655579, "url": "https://libraries.minecraft.net/it/unimi/dsi/fastutil/7.1.0/fastutil-7.1.0.jar"}}, "name": "it.unimi.dsi:fastutil:7.1.0"}, {"downloads": {"artifact": {"path": "org/apache/logging/log4j/log4j-api/2.8.1/log4j-api-2.8.1.jar", "sha1": "e801d13612e22cad62a3f4f3fe7fdbe6334a8e72", "size": 228859, "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.8.1/log4j-api-2.8.1.jar"}}, "name": "org.apache.logging.log4j:log4j-api:2.8.1"}, {"downloads": {"artifact": {"path": "org/apache/logging/log4j/log4j-core/2.8.1/log4j-core-2.8.1.jar", "sha1": "4ac28ff2f1ddf05dae3043a190451e8c46b73c31", "size": 1402925, "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.8.1/log4j-core-2.8.1.jar"}}, "name": "org.apache.logging.log4j:log4j-core:2.8.1"}, {"downloads": {"artifact": {"path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar", "sha1": "697517568c68e78ae0b4544145af031c81082dfe", "size": 1047168, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar"}}, "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209", "rules": [{"action": "allow"}, {"action": "disallow", "os": {"name": "osx"}}]}, {"downloads": {"artifact": {"path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar", "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0", "size": 173887, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar"}}, "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209", "rules": [{"action": "allow"}, {"action": "disallow", "os": {"name": "osx"}}]}, {"downloads": {"artifact": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", "size": 22, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"}, "classifiers": {"natives-linux": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", "size": 578680, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar"}, "natives-osx": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", "size": 426822, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"}, "natives-windows": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", "size": 613748, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"}}}, "extract": {"exclude": ["META-INF/"]}, "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", "natives": {"linux": "natives-linux", "osx": "natives-osx", "windows": "natives-windows"}, "rules": [{"action": "allow"}, {"action": "disallow", "os": {"name": "osx"}}]}, {"downloads": {"artifact": {"path": "org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar", "sha1": "7707204c9ffa5d91662de95f0a224e2f721b22af", "size": 1045632, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.2-nightly-20140822/lwjgl-2.9.2-nightly-20140822.jar"}}, "name": "org.lwjgl.lwjgl:lwjgl:2.9.2-nightly-20140822", "rules": [{"action": "allow", "os": {"name": "osx"}}]}, {"downloads": {"artifact": {"path": "org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar", "sha1": "f0e612c840a7639c1f77f68d72a28dae2f0c8490", "size": 173887, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.2-nightly-20140822/lwjgl_util-2.9.2-nightly-20140822.jar"}}, "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.2-nightly-20140822", "rules": [{"action": "allow", "os": {"name": "osx"}}]}, {"downloads": {"classifiers": {"natives-linux": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar", "sha1": "d898a33b5d0a6ef3fed3a4ead506566dce6720a5", "size": 578539, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-linux.jar"}, "natives-osx": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar", "sha1": "79f5ce2fea02e77fe47a3c745219167a542121d7", "size": 468116, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-osx.jar"}, "natives-windows": {"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar", "sha1": "78b2a55ce4dc29c6b3ec4df8ca165eba05f9b341", "size": 613680, "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.2-nightly-20140822/lwjgl-platform-2.9.2-nightly-20140822-natives-windows.jar"}}}, "extract": {"exclude": ["META-INF/"]}, "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822", "natives": {"linux": "natives-linux", "osx": "natives-osx", "windows": "natives-windows"}, "rules": [{"action": "allow", "os": {"name": "osx"}}]}, {"downloads": {"classifiers": {"natives-linux": {"path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar", "sha1": "7ff832a6eb9ab6a767f1ade2b548092d0fa64795", "size": 10362, "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar"}, "natives-osx": {"path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar", "sha1": "53f9c919f34d2ca9de8c51fc4e1e8282029a9232", "size": 12186, "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar"}, "natives-windows": {"path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar", "sha1": "385ee093e01f587f30ee1c8a2ee7d408fd732e16", "size": 155179, "url": "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-windows.jar"}}}, "extract": {"exclude": ["META-INF/"]}, "name": "net.java.jinput:jinput-platform:2.0.5", "natives": {"linux": "natives-linux", "osx": "natives-osx", "windows": "natives-windows"}}, {"downloads": {"artifact": {"path": "com/mojang/text2speech/1.10.3/text2speech-1.10.3.jar", "sha1": "48fd510879dff266c3815947de66e3d4809f8668", "size": 11055, "url": "https://libraries.minecraft.net/com/mojang/text2speech/1.10.3/text2speech-1.10.3.jar"}}, "name": "com.mojang:text2speech:1.10.3"}, {"downloads": {"artifact": {"path": "com/mojang/text2speech/1.10.3/text2speech-1.10.3.jar", "sha1": "48fd510879dff266c3815947de66e3d4809f8668", "size": 11055, "url": "https://libraries.minecraft.net/com/mojang/text2speech/1.10.3/text2speech-1.10.3.jar"}, "classifiers": {"natives-linux": {"path": "com/mojang/text2speech/1.10.3/text2speech-1.10.3-natives-linux.jar", "sha1": "ab7896aec3b3dd272b06194357f2d98f832c0cfc", "size": 7833, "url": "https://libraries.minecraft.net/com/mojang/text2speech/1.10.3/text2speech-1.10.3-natives-linux.jar"}, "natives-windows": {"path": "com/mojang/text2speech/1.10.3/text2speech-1.10.3-natives-windows.jar", "sha1": "84a4b856389cc4f485275b1f63497a95a857a443", "size": 81217, "url": "https://libraries.minecraft.net/com/mojang/text2speech/1.10.3/text2speech-1.10.3-natives-windows.jar"}, "sources": {"path": "com/mojang/text2speech/1.10.3/text2speech-1.10.3-sources.jar", "sha1": "404339fe43d1011ee046a249b0ec7ae9ce04a834", "size": 4632, "url": "https://libraries.minecraft.net/com/mojang/text2speech/1.10.3/text2speech-1.10.3-sources.jar"}}}, "extract": {"exclude": ["META-INF/"]}, "name": "com.mojang:text2speech:1.10.3", "natives": {"linux": "natives-linux", "windows": "natives-windows"}}, {"downloads": {"artifact": {"path": "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar", "sha1": "6ef160c3133a78de015830860197602ca1c855d3", "size": 40502, "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar"}, "classifiers": {"javadoc": {"path": "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-javadoc.jar", "sha1": "fb0092f22cb4fe8e631452f577b7a238778abf2a", "size": 174060, "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-javadoc.jar"}, "natives-osx": {"path": "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar", "sha1": "08befab4894d55875f33c3d300f4f71e6e828f64", "size": 5629, "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar"}, "sources": {"path": "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-sources.jar", "sha1": "865837a198189aee737019561ece842827f24278", "size": 43283, "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-sources.jar"}}}, "extract": {"exclude": ["META-INF/"]}, "name": "ca.weblite:java-objc-bridge:1.0.0", "natives": {"osx": "natives-osx"}, "rules": [{"action": "allow", "os": {"name": "osx"}}]}, {"downloads": {"artifact": {"path": "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar", "sha1": "6ef160c3133a78de015830860197602ca1c855d3", "size": 40502, "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar"}}, "name": "ca.weblite:java-objc-bridge:1.0.0", "rules": [{"action": "allow", "os": {"name": "osx"}}]}], "logging": {"client": {"argument": "-Dlog4j.configurationFile=${path}", "file": {"id": "client-1.12.xml", "sha1": "ef4f57b922df243d0cef096efe808c72db042149", "size": 877, "url": "https://launcher.mojang.com/v1/objects/ef4f57b922df243d0cef096efe808c72db042149/client-1.12.xml"}, "type": "log4j2-xml"}}, "mainClass": "net.minecraft.client.main.Main", "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}", "minimumLauncherVersion": 18, "releaseTime": "2017-09-18T08:39:46+00:00", "time": "2017-09-18T08:39:46+00:00", "type": "release"} diff --git a/patches/net/minecraft/advancements/Advancement.java.patch b/patches/net/minecraft/advancements/Advancement.java.patch new file mode 100644 index 00000000..c8e619fb --- /dev/null +++ b/patches/net/minecraft/advancements/Advancement.java.patch @@ -0,0 +1,54 @@ +--- ../src-base/minecraft/net/minecraft/advancements/Advancement.java ++++ ../src-work/minecraft/net/minecraft/advancements/Advancement.java +@@ -32,6 +32,7 @@ + private final String[][] requirements; + private final Set children = Sets.newLinkedHashSet(); + private final ITextComponent displayText; ++ public final org.bukkit.advancement.Advancement bukkit = new org.bukkit.craftbukkit.advancement.CraftAdvancement(this); + + public Advancement(ResourceLocation id, @Nullable Advancement parentIn, @Nullable DisplayInfo displayIn, AdvancementRewards rewardsIn, Map criteriaIn, String[][] requirementsIn) + { +@@ -68,9 +69,9 @@ + } + } + +- public Advancement.Builder copy() ++ public Builder copy() + { +- return new Advancement.Builder(this.parent == null ? null : this.parent.getId(), this.display, this.rewards, this.criteria, this.requirements); ++ return new Builder(this.parent == null ? null : this.parent.getId(), this.display, this.rewards, this.criteria, this.requirements); + } + + @Nullable +@@ -230,7 +231,7 @@ + return "Task Advancement{parentId=" + this.parentId + ", display=" + this.display + ", rewards=" + this.rewards + ", criteria=" + this.criteria + ", requirements=" + Arrays.deepToString(this.requirements) + '}'; + } + +- public static Advancement.Builder deserialize(JsonObject json, JsonDeserializationContext context) ++ public static Builder deserialize(JsonObject json, JsonDeserializationContext context) + { + ResourceLocation resourcelocation = json.has("parent") ? new ResourceLocation(JsonUtils.getString(json, "parent")) : null; + DisplayInfo displayinfo = json.has("display") ? DisplayInfo.deserialize(JsonUtils.getJsonObject(json, "display"), context) : null; +@@ -303,11 +304,11 @@ + } + } + +- return new Advancement.Builder(resourcelocation, displayinfo, advancementrewards, map, astring); ++ return new Builder(resourcelocation, displayinfo, advancementrewards, map, astring); + } + } + +- public static Advancement.Builder readFrom(PacketBuffer buf) throws IOException ++ public static Builder readFrom(PacketBuffer buf) throws IOException + { + ResourceLocation resourcelocation = buf.readBoolean() ? buf.readResourceLocation() : null; + DisplayInfo displayinfo = buf.readBoolean() ? DisplayInfo.read(buf) : null; +@@ -324,7 +325,7 @@ + } + } + +- return new Advancement.Builder(resourcelocation, displayinfo, AdvancementRewards.EMPTY, map, astring); ++ return new Builder(resourcelocation, displayinfo, AdvancementRewards.EMPTY, map, astring); + } + } + } diff --git a/patches/net/minecraft/advancements/AdvancementManager.java.patch b/patches/net/minecraft/advancements/AdvancementManager.java.patch new file mode 100644 index 00000000..cf4463aa --- /dev/null +++ b/patches/net/minecraft/advancements/AdvancementManager.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/advancements/AdvancementManager.java ++++ ../src-work/minecraft/net/minecraft/advancements/AdvancementManager.java +@@ -50,8 +50,8 @@ + return Advancement.Builder.deserialize(jsonobject, p_deserialize_3_); + } + }).registerTypeAdapter(AdvancementRewards.class, new AdvancementRewards.Deserializer()).registerTypeHierarchyAdapter(ITextComponent.class, new ITextComponent.Serializer()).registerTypeHierarchyAdapter(Style.class, new Style.Serializer()).registerTypeAdapterFactory(new EnumTypeAdapterFactory()).create(); +- private static final AdvancementList ADVANCEMENT_LIST = new AdvancementList(); +- private final File advancementsDir; ++ public static final AdvancementList ADVANCEMENT_LIST = new AdvancementList(); ++ public final File advancementsDir; + private boolean hasErrored; + + public AdvancementManager(@Nullable File advancementsDirIn) +@@ -173,7 +173,11 @@ + Path path2 = path.relativize(path1); + String s = FilenameUtils.removeExtension(path2.toString()).replaceAll("\\\\", "/"); + ResourceLocation resourcelocation = new ResourceLocation("minecraft", s); +- ++ // Spigot start ++ if (org.spigotmc.SpigotConfig.disabledAdvancements != null && (org.spigotmc.SpigotConfig.disabledAdvancements.contains("*") || org.spigotmc.SpigotConfig.disabledAdvancements.contains(resourcelocation.toString()))) { ++ continue; ++ } ++ // Spigot end + if (!map.containsKey(resourcelocation)) + { + BufferedReader bufferedreader = null; diff --git a/patches/net/minecraft/advancements/AdvancementRewards.java.patch b/patches/net/minecraft/advancements/AdvancementRewards.java.patch new file mode 100644 index 00000000..66bb9a54 --- /dev/null +++ b/patches/net/minecraft/advancements/AdvancementRewards.java.patch @@ -0,0 +1,116 @@ +--- ../src-base/minecraft/net/minecraft/advancements/AdvancementRewards.java ++++ ../src-work/minecraft/net/minecraft/advancements/AdvancementRewards.java +@@ -89,56 +89,67 @@ + + if (functionobject != null) + { +- ICommandSender icommandsender = new ICommandSender() +- { +- public String getName() +- { +- return player.getName(); +- } +- public ITextComponent getDisplayName() +- { +- return player.getDisplayName(); +- } +- public void sendMessage(ITextComponent component) +- { +- } +- public boolean canUseCommand(int permLevel, String commandName) +- { +- return permLevel <= 2; +- } +- public BlockPos getPosition() +- { +- return player.getPosition(); +- } +- public Vec3d getPositionVector() +- { +- return player.getPositionVector(); +- } +- public World getEntityWorld() +- { +- return player.world; +- } +- public Entity getCommandSenderEntity() +- { +- return player; +- } +- public boolean sendCommandFeedback() +- { +- return minecraftserver.worlds[0].getGameRules().getBoolean("commandBlockOutput"); +- } +- public void setCommandStat(CommandResultStats.Type type, int amount) +- { +- player.setCommandStat(type, amount); +- } +- public MinecraftServer getServer() +- { +- return player.getServer(); +- } +- }; ++ ICommandSender icommandsender = new AdvancementCommandListener(player, minecraftserver); ++ + minecraftserver.getFunctionManager().execute(functionobject, icommandsender); + } ++ + } + ++ public static class AdvancementCommandListener implements ICommandSender { ++ ++ private final EntityPlayerMP player; ++ private final MinecraftServer minecraftserver; ++ ++ public AdvancementCommandListener(EntityPlayerMP entityplayer, MinecraftServer minecraftserver) { ++ this.player = entityplayer; ++ this.minecraftserver = minecraftserver; ++ } ++ ++ public String getName() { ++ return player.getName(); ++ } ++ ++ public ITextComponent getDisplayName() { ++ return player.getDisplayName(); ++ } ++ ++ public void sendMessage(ITextComponent component) { ++ } ++ ++ public boolean canUseCommand(int permLevel, String commandName) { ++ return permLevel <= 2; ++ } ++ ++ public BlockPos getPosition() { ++ return player.getPosition(); ++ } ++ ++ public Vec3d getPositionVector() { ++ return player.getPositionVector(); ++ } ++ ++ public World getEntityWorld() { ++ return player.world; ++ } ++ ++ public Entity getCommandSenderEntity() { ++ return player; ++ } ++ ++ public boolean sendCommandFeedback() { ++ return minecraftserver.worlds[0].getGameRules().getBoolean("commandBlockOutput"); ++ } ++ ++ public void setCommandStat(CommandResultStats.Type type, int amount) { ++ player.setCommandStat(type, amount); ++ } ++ ++ public MinecraftServer getServer() { ++ return player.getServer(); ++ } ++ } ++ + public String toString() + { + return "AdvancementRewards{experience=" + this.experience + ", loot=" + Arrays.toString((Object[])this.loot) + ", recipes=" + Arrays.toString((Object[])this.recipes) + ", function=" + this.function + '}'; diff --git a/patches/net/minecraft/advancements/FunctionManager.java.patch b/patches/net/minecraft/advancements/FunctionManager.java.patch new file mode 100644 index 00000000..fb6abcd4 --- /dev/null +++ b/patches/net/minecraft/advancements/FunctionManager.java.patch @@ -0,0 +1,82 @@ +--- ../src-base/minecraft/net/minecraft/advancements/FunctionManager.java ++++ ../src-work/minecraft/net/minecraft/advancements/FunctionManager.java +@@ -27,10 +27,13 @@ + private final Map functions = Maps.newHashMap(); + private String currentGameLoopFunctionId = "-"; + private FunctionObject gameLoopFunction; +- private final ArrayDeque commandQueue = new ArrayDeque(); ++ private final ArrayDeque commandQueue = new ArrayDeque(); + private boolean isExecuting = false; +- private final ICommandSender gameLoopFunctionSender = new ICommandSender() +- { ++ private final ICommandSender gameLoopFunctionSender = new CustomFunctionListener(); ++ ++ public class CustomFunctionListener implements ICommandSender { ++ ++ public org.bukkit.command.CommandSender sender = new org.bukkit.craftbukkit.command.CraftFunctionCommandSender(this); + public String getName() + { + return FunctionManager.this.currentGameLoopFunctionId; +@@ -101,7 +104,7 @@ + { + if (this.commandQueue.size() < i) + { +- this.commandQueue.addFirst(new FunctionManager.QueuedCommand(this, sender, new FunctionObject.FunctionEntry(function))); ++ this.commandQueue.addFirst(new QueuedCommand(this, sender, new FunctionObject.FunctionEntry(function))); + } + + return 0; +@@ -118,7 +121,7 @@ + + for (int k = afunctionobject$entry.length - 1; k >= 0; --k) + { +- this.commandQueue.push(new FunctionManager.QueuedCommand(this, sender, afunctionobject$entry[k])); ++ this.commandQueue.push(new QueuedCommand(this, sender, afunctionobject$entry[k])); + } + + while (true) +@@ -192,26 +195,26 @@ + } + + public static class QueuedCommand ++ { ++ private final FunctionManager functionManager; ++ private final ICommandSender sender; ++ private final FunctionObject.Entry entry; ++ ++ public QueuedCommand(FunctionManager functionManagerIn, ICommandSender senderIn, FunctionObject.Entry entryIn) + { +- private final FunctionManager functionManager; +- private final ICommandSender sender; +- private final FunctionObject.Entry entry; ++ this.functionManager = functionManagerIn; ++ this.sender = senderIn; ++ this.entry = entryIn; ++ } + +- public QueuedCommand(FunctionManager functionManagerIn, ICommandSender senderIn, FunctionObject.Entry entryIn) +- { +- this.functionManager = functionManagerIn; +- this.sender = senderIn; +- this.entry = entryIn; +- } ++ public void execute(ArrayDeque commandQueue, int maxCommandChainLength) ++ { ++ this.entry.execute(this.functionManager, this.sender, commandQueue, maxCommandChainLength); ++ } + +- public void execute(ArrayDeque commandQueue, int maxCommandChainLength) +- { +- this.entry.execute(this.functionManager, this.sender, commandQueue, maxCommandChainLength); +- } +- +- public String toString() +- { +- return this.entry.toString(); +- } ++ public String toString() ++ { ++ return this.entry.toString(); + } ++ } + } diff --git a/patches/net/minecraft/advancements/PlayerAdvancements.java.patch b/patches/net/minecraft/advancements/PlayerAdvancements.java.patch new file mode 100644 index 00000000..e969a4b1 --- /dev/null +++ b/patches/net/minecraft/advancements/PlayerAdvancements.java.patch @@ -0,0 +1,52 @@ +--- ../src-base/minecraft/net/minecraft/advancements/PlayerAdvancements.java ++++ ../src-work/minecraft/net/minecraft/advancements/PlayerAdvancements.java +@@ -16,7 +16,6 @@ + import java.util.Map; + import java.util.Set; + import java.util.Map.Entry; +-import java.util.function.Function; + import java.util.stream.Collectors; + import java.util.stream.Stream; + import javax.annotation.Nullable; +@@ -29,6 +28,7 @@ + import net.minecraft.util.text.TextComponentTranslation; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.spigotmc.SpigotConfig; + + public class PlayerAdvancements + { +@@ -142,8 +142,12 @@ + + if (advancement == null) + { ++ // CraftBukkit start ++ if (entry.getKey().toString().equals("minecraft")) { + LOGGER.warn("Ignored advancement '" + entry.getKey() + "' in progress file " + this.progressFile + " - it doesn't exist anymore?"); + } ++ // CraftBukkit end ++ } + else + { + this.startProgress(advancement, entry.getValue()); +@@ -167,6 +171,12 @@ + + public void save() + { ++ // Spigot start ++ if (SpigotConfig.disableAdvancementSaving) { ++ return; ++ } ++ // Spigot end ++ + Map map = Maps.newHashMap(); + + for (Entry entry : this.progress.entrySet()) +@@ -211,6 +221,7 @@ + + if (!flag1 && advancementprogress.isDone()) + { ++ this.player.world.getServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), p_192750_1_.bukkit)); // CraftBukkit + p_192750_1_.getRewards().apply(this.player); + + if (p_192750_1_.getDisplay() != null && p_192750_1_.getDisplay().shouldAnnounceToChat() && this.player.world.getGameRules().getBoolean("announceAdvancements")) diff --git a/patches/net/minecraft/block/Block.java.patch b/patches/net/minecraft/block/Block.java.patch new file mode 100644 index 00000000..72e75d49 --- /dev/null +++ b/patches/net/minecraft/block/Block.java.patch @@ -0,0 +1,342 @@ +--- ../src-base/minecraft/net/minecraft/block/Block.java ++++ ../src-work/minecraft/net/minecraft/block/Block.java +@@ -1,10 +1,7 @@ + package net.minecraft.block; + +-import com.google.common.collect.Sets; +-import com.google.common.collect.UnmodifiableIterator; + import java.util.List; + import java.util.Random; +-import java.util.Set; + import javax.annotation.Nullable; + import net.minecraft.block.material.EnumPushReaction; + import net.minecraft.block.material.MapColor; +@@ -21,6 +18,7 @@ + import net.minecraft.entity.item.EntityItem; + import net.minecraft.entity.item.EntityXPOrb; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.init.Enchantments; + import net.minecraft.init.Items; +@@ -573,7 +571,8 @@ + + for (ItemStack drop : drops) + { +- if (worldIn.rand.nextFloat() <= chance) ++ // CraftBukkit - <= to < to allow for plugins to completely disable block drops from explosions ++ if (worldIn.rand.nextFloat() < chance) + { + spawnAsEntity(worldIn, pos, drop); + } +@@ -596,12 +595,27 @@ + double d2 = (double)(worldIn.rand.nextFloat() * 0.5F) + 0.25D; + EntityItem entityitem = new EntityItem(worldIn, (double)pos.getX() + d0, (double)pos.getY() + d1, (double)pos.getZ() + d2, stack); + entityitem.setDefaultPickupDelay(); +- worldIn.spawnEntity(entityitem); ++ if (worldIn.captureDrops != null) { ++ worldIn.captureDrops.add(entityitem); ++ } else { ++ worldIn.spawnEntity(entityitem); ++ } + } + } + ++ // CraftBukkit start ++ public int getExpDrop(World world, IBlockState state, int enchantmentLevel) { ++ return 0; ++ } ++ // CraftBukkit end ++ + public void dropXpOnBlockBreak(World worldIn, BlockPos pos, int amount) + { ++ dropXpOnBlockBreak(worldIn, pos, amount, null); ++ } ++ ++ public void dropXpOnBlockBreak(World worldIn, BlockPos pos, int amount, EntityPlayerMP player) ++ { + if (!worldIn.isRemote && worldIn.getGameRules().getBoolean("doTileDrops")) + { + while (amount > 0) +@@ -714,7 +728,7 @@ + + if (this.canSilkHarvest(worldIn, pos, state, player) && EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, stack) > 0) + { +- java.util.List items = new java.util.ArrayList(); ++ List items = new java.util.ArrayList(); + ItemStack itemstack = this.getSilkTouchDrop(state); + + if (!itemstack.isEmpty()) +@@ -915,24 +929,24 @@ + return this.defaultBlockState; + } + +- public Block.EnumOffsetType getOffsetType() ++ public EnumOffsetType getOffsetType() + { +- return Block.EnumOffsetType.NONE; ++ return EnumOffsetType.NONE; + } + + @Deprecated + public Vec3d getOffset(IBlockState state, IBlockAccess worldIn, BlockPos pos) + { +- Block.EnumOffsetType block$enumoffsettype = this.getOffsetType(); ++ EnumOffsetType block$enumoffsettype = this.getOffsetType(); + +- if (block$enumoffsettype == Block.EnumOffsetType.NONE) ++ if (block$enumoffsettype == EnumOffsetType.NONE) + { + return Vec3d.ZERO; + } + else + { + long i = MathHelper.getCoordinateRandom(pos.getX(), 0, pos.getZ()); +- return new Vec3d(((double)((float)(i >> 16 & 15L) / 15.0F) - 0.5D) * 0.5D, block$enumoffsettype == Block.EnumOffsetType.XYZ ? ((double)((float)(i >> 20 & 15L) / 15.0F) - 1.0D) * 0.2D : 0.0D, ((double)((float)(i >> 24 & 15L) / 15.0F) - 0.5D) * 0.5D); ++ return new Vec3d(((double)((float)(i >> 16 & 15L) / 15.0F) - 0.5D) * 0.5D, block$enumoffsettype == EnumOffsetType.XYZ ? ((double)((float)(i >> 20 & 15L) / 15.0F) - 1.0D) * 0.2D : 0.0D, ((double)((float)(i >> 24 & 15L) / 15.0F) - 0.5D) * 0.5D); + } + } + +@@ -956,7 +970,7 @@ + //For ForgeInternal use Only! + protected ThreadLocal harvesters = new ThreadLocal(); + private ThreadLocal silk_check_state = new ThreadLocal(); +- protected static java.util.Random RANDOM = new java.util.Random(); // Useful for random things without a seed. ++ protected static Random RANDOM = new Random(); // Useful for random things without a seed. + + /** + * Gets the slipperiness at the given location at the given state. Normally +@@ -1176,7 +1190,7 @@ + public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest) + { + this.onBlockHarvested(world, pos, state, player); +- return world.setBlockState(pos, net.minecraft.init.Blocks.AIR.getDefaultState(), world.isRemote ? 11 : 3); ++ return world.setBlockState(pos, Blocks.AIR.getDefaultState(), world.isRemote ? 11 : 3); + } + + /** +@@ -1190,7 +1204,7 @@ + */ + public int getFlammability(IBlockAccess world, BlockPos pos, EnumFacing face) + { +- return net.minecraft.init.Blocks.FIRE.getFlammability(this); ++ return Blocks.FIRE.getFlammability(this); + } + + /** +@@ -1218,7 +1232,7 @@ + */ + public int getFireSpreadSpeed(IBlockAccess world, BlockPos pos, EnumFacing face) + { +- return net.minecraft.init.Blocks.FIRE.getEncouragement(this); ++ return Blocks.FIRE.getEncouragement(this); + } + + /** +@@ -1370,7 +1384,7 @@ + */ + public boolean isBed(IBlockState state, IBlockAccess world, BlockPos pos, @Nullable Entity player) + { +- return this == net.minecraft.init.Blocks.BED; ++ return this == Blocks.BED; + } + + /** +@@ -1564,18 +1578,13 @@ + */ + public boolean canPlaceTorchOnTop(IBlockState state, IBlockAccess world, BlockPos pos) + { +- if (this == Blocks.END_GATEWAY || this == Blocks.LIT_PUMPKIN) ++ if (state.isTopSolid() || state.getBlockFaceShape(world, pos, EnumFacing.UP) == BlockFaceShape.SOLID) + { +- return false; ++ return this != Blocks.END_GATEWAY && this != Blocks.LIT_PUMPKIN; + } +- else if (state.isTopSolid() || this instanceof BlockFence || this == Blocks.GLASS || this == Blocks.COBBLESTONE_WALL || this == Blocks.STAINED_GLASS) +- { +- return true; +- } + else + { +- BlockFaceShape shape = state.getBlockFaceShape(world, pos, EnumFacing.UP); +- return (shape == BlockFaceShape.SOLID || shape == BlockFaceShape.CENTER || shape == BlockFaceShape.CENTER_BIG) && !isExceptionBlockForAttaching(this); ++ return this instanceof BlockFence || this == Blocks.GLASS || this == Blocks.COBBLESTONE_WALL || this == Blocks.STAINED_GLASS; + } + } + +@@ -1693,12 +1702,12 @@ + IBlockState plant = plantable.getPlant(world, pos.offset(direction)); + net.minecraftforge.common.EnumPlantType plantType = plantable.getPlantType(world, pos.offset(direction)); + +- if (plant.getBlock() == net.minecraft.init.Blocks.CACTUS) ++ if (plant.getBlock() == Blocks.CACTUS) + { +- return this == net.minecraft.init.Blocks.CACTUS || this == net.minecraft.init.Blocks.SAND; ++ return this == Blocks.CACTUS || this == Blocks.SAND; + } + +- if (plant.getBlock() == net.minecraft.init.Blocks.REEDS && this == net.minecraft.init.Blocks.REEDS) ++ if (plant.getBlock() == Blocks.REEDS && this == Blocks.REEDS) + { + return true; + } +@@ -1710,14 +1719,14 @@ + + switch (plantType) + { +- case Desert: return this == net.minecraft.init.Blocks.SAND || this == net.minecraft.init.Blocks.HARDENED_CLAY || this == net.minecraft.init.Blocks.STAINED_HARDENED_CLAY; +- case Nether: return this == net.minecraft.init.Blocks.SOUL_SAND; +- case Crop: return this == net.minecraft.init.Blocks.FARMLAND; ++ case Desert: return this == Blocks.SAND || this == Blocks.HARDENED_CLAY || this == Blocks.STAINED_HARDENED_CLAY; ++ case Nether: return this == Blocks.SOUL_SAND; ++ case Crop: return this == Blocks.FARMLAND; + case Cave: return state.isSideSolid(world, pos, EnumFacing.UP); +- case Plains: return this == net.minecraft.init.Blocks.GRASS || this == net.minecraft.init.Blocks.DIRT || this == net.minecraft.init.Blocks.FARMLAND; ++ case Plains: return this == Blocks.GRASS || this == Blocks.DIRT || this == Blocks.FARMLAND; + case Water: return state.getMaterial() == Material.WATER && state.getValue(BlockLiquid.LEVEL) == 0; + case Beach: +- boolean isBeach = this == net.minecraft.init.Blocks.GRASS || this == net.minecraft.init.Blocks.DIRT || this == net.minecraft.init.Blocks.SAND; ++ boolean isBeach = this == Blocks.GRASS || this == Blocks.DIRT || this == Blocks.SAND; + boolean hasWater = (world.getBlockState(pos.east()).getMaterial() == Material.WATER || + world.getBlockState(pos.west()).getMaterial() == Material.WATER || + world.getBlockState(pos.north()).getMaterial() == Material.WATER || +@@ -1744,9 +1753,9 @@ + */ + public void onPlantGrow(IBlockState state, World world, BlockPos pos, BlockPos source) + { +- if (this == net.minecraft.init.Blocks.GRASS || this == net.minecraft.init.Blocks.FARMLAND) ++ if (this == Blocks.GRASS || this == Blocks.FARMLAND) + { +- world.setBlockState(pos, net.minecraft.init.Blocks.DIRT.getDefaultState(), 2); ++ world.setBlockState(pos, Blocks.DIRT.getDefaultState(), 2); + } + } + +@@ -1761,7 +1770,7 @@ + */ + public boolean isFertile(World world, BlockPos pos) + { +- if (this == net.minecraft.init.Blocks.FARMLAND) ++ if (this == Blocks.FARMLAND) + { + return ((Integer)world.getBlockState(pos).getValue(BlockFarmland.MOISTURE)) > 0; + } +@@ -1799,17 +1808,17 @@ + { + if (entity instanceof net.minecraft.entity.boss.EntityDragon) + { +- return this != net.minecraft.init.Blocks.BARRIER && +- this != net.minecraft.init.Blocks.OBSIDIAN && +- this != net.minecraft.init.Blocks.END_STONE && +- this != net.minecraft.init.Blocks.BEDROCK && +- this != net.minecraft.init.Blocks.END_PORTAL && +- this != net.minecraft.init.Blocks.END_PORTAL_FRAME && +- this != net.minecraft.init.Blocks.COMMAND_BLOCK && +- this != net.minecraft.init.Blocks.REPEATING_COMMAND_BLOCK && +- this != net.minecraft.init.Blocks.CHAIN_COMMAND_BLOCK && +- this != net.minecraft.init.Blocks.IRON_BARS && +- this != net.minecraft.init.Blocks.END_GATEWAY; ++ return this != Blocks.BARRIER && ++ this != Blocks.OBSIDIAN && ++ this != Blocks.END_STONE && ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.IRON_BARS && ++ this != Blocks.END_GATEWAY; + } + else if ((entity instanceof net.minecraft.entity.boss.EntityWither) || + (entity instanceof net.minecraft.entity.projectile.EntityWitherSkull)) +@@ -1830,7 +1839,7 @@ + */ + public boolean isBeaconBase(IBlockAccess worldObj, BlockPos pos, BlockPos beacon) + { +- return this == net.minecraft.init.Blocks.EMERALD_BLOCK || this == net.minecraft.init.Blocks.GOLD_BLOCK || this == net.minecraft.init.Blocks.DIAMOND_BLOCK || this == net.minecraft.init.Blocks.IRON_BLOCK; ++ return this == Blocks.EMERALD_BLOCK || this == Blocks.GOLD_BLOCK || this == Blocks.DIAMOND_BLOCK || this == Blocks.IRON_BLOCK; + } + + /** +@@ -1918,7 +1927,7 @@ + */ + public float getEnchantPowerBonus(World world, BlockPos pos) + { +- return this == net.minecraft.init.Blocks.BOOKSHELF ? 1 : 0; ++ return this == Blocks.BOOKSHELF ? 1 : 0; + } + + /** +@@ -1930,14 +1939,14 @@ + * @return If the recoloring was successful + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) +- public boolean recolorBlock(World world, BlockPos pos, EnumFacing side, net.minecraft.item.EnumDyeColor color) ++ public boolean recolorBlock(World world, BlockPos pos, EnumFacing side, EnumDyeColor color) + { + IBlockState state = world.getBlockState(pos); + for (IProperty prop : state.getProperties().keySet()) + { +- if (prop.getName().equals("color") && prop.getValueClass() == net.minecraft.item.EnumDyeColor.class) ++ if (prop.getName().equals("color") && prop.getValueClass() == EnumDyeColor.class) + { +- net.minecraft.item.EnumDyeColor current = (net.minecraft.item.EnumDyeColor)state.getValue(prop); ++ EnumDyeColor current = (EnumDyeColor)state.getValue(prop); + if (current != color && prop.getAllowedValues().contains(color)) + { + world.setBlockState(pos, state.withProperty(prop, color)); +@@ -2074,7 +2083,7 @@ + */ + public boolean isToolEffective(String type, IBlockState state) + { +- if ("pickaxe".equals(type) && (this == net.minecraft.init.Blocks.REDSTONE_ORE || this == net.minecraft.init.Blocks.LIT_REDSTONE_ORE || this == net.minecraft.init.Blocks.OBSIDIAN)) ++ if ("pickaxe".equals(type) && (this == Blocks.REDSTONE_ORE || this == Blocks.LIT_REDSTONE_ORE || this == Blocks.OBSIDIAN)) + return false; + return type != null && type.equals(getHarvestTool(state)); + } +@@ -2286,23 +2295,15 @@ + return false; + } + +- /** @deprecated use {@link #getAiPathNodeType(IBlockState, IBlockAccess, BlockPos, net.minecraft.entity.EntityLiving)} */ +- @Nullable +- @Deprecated // TODO: remove +- public net.minecraft.pathfinding.PathNodeType getAiPathNodeType(IBlockState state, IBlockAccess world, BlockPos pos) +- { +- return isBurning(world, pos) ? net.minecraft.pathfinding.PathNodeType.DAMAGE_FIRE : null; +- } +- + /** + * Get the {@code PathNodeType} for this block. Return {@code null} for vanilla behavior. + * + * @return the PathNodeType + */ + @Nullable +- public net.minecraft.pathfinding.PathNodeType getAiPathNodeType(IBlockState state, IBlockAccess world, BlockPos pos, @Nullable net.minecraft.entity.EntityLiving entity) ++ public net.minecraft.pathfinding.PathNodeType getAiPathNodeType(IBlockState state, IBlockAccess world, BlockPos pos) + { +- return getAiPathNodeType(state, world, pos); ++ return isBurning(world, pos) ? net.minecraft.pathfinding.PathNodeType.DAMAGE_FIRE : null; + } + + /** +@@ -2642,6 +2643,18 @@ + registerBlock(id, new ResourceLocation(textualID), block_); + } + ++ // Spigot start ++ public static float range(float min, float value, float max) { ++ if (value < min) { ++ return min; ++ } ++ if (value > max) { ++ return max; ++ } ++ return value; ++ } ++ // Spigot end ++ + public static enum EnumOffsetType + { + NONE, diff --git a/patches/net/minecraft/block/BlockBasePressurePlate.java.patch b/patches/net/minecraft/block/BlockBasePressurePlate.java.patch new file mode 100644 index 00000000..a22da055 --- /dev/null +++ b/patches/net/minecraft/block/BlockBasePressurePlate.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockBasePressurePlate.java ++++ ../src-work/minecraft/net/minecraft/block/BlockBasePressurePlate.java +@@ -14,6 +14,7 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public abstract class BlockBasePressurePlate extends Block + { +@@ -125,6 +126,17 @@ + boolean flag = oldRedstoneStrength > 0; + boolean flag1 = i > 0; + ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.plugin.PluginManager manager = worldIn.getServer().getPluginManager(); ++ ++ if (flag != flag1) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), oldRedstoneStrength, i); ++ manager.callEvent(eventRedstone); ++ ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ i = eventRedstone.getNewCurrent(); ++ } ++ + if (oldRedstoneStrength != i) + { + state = this.setRedstoneStrength(state, i); diff --git a/patches/net/minecraft/block/BlockBush.java.patch b/patches/net/minecraft/block/BlockBush.java.patch new file mode 100644 index 00000000..a6bb3822 --- /dev/null +++ b/patches/net/minecraft/block/BlockBush.java.patch @@ -0,0 +1,30 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockBush.java ++++ ../src-work/minecraft/net/minecraft/block/BlockBush.java +@@ -41,7 +41,7 @@ + public boolean canPlaceBlockAt(World worldIn, BlockPos pos) + { + IBlockState soil = worldIn.getBlockState(pos.down()); +- return super.canPlaceBlockAt(worldIn, pos) && soil.getBlock().canSustainPlant(soil, worldIn, pos.down(), net.minecraft.util.EnumFacing.UP, this); ++ return super.canPlaceBlockAt(worldIn, pos) && soil.getBlock().canSustainPlant(soil, worldIn, pos.down(), EnumFacing.UP, this); + } + + protected boolean canSustainBush(IBlockState state) +@@ -64,6 +64,9 @@ + { + if (!this.canBlockStay(worldIn, pos, state)) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(worldIn, pos).isCancelled()) { ++ return; ++ } + this.dropBlockAsItem(worldIn, pos, state, 0); + worldIn.setBlockState(pos, Blocks.AIR.getDefaultState(), 3); + } +@@ -74,7 +77,7 @@ + if (state.getBlock() == this) //Forge: This function is called during world gen and placement, before this block is set, so if we are not 'here' then assume it's the pre-check. + { + IBlockState soil = worldIn.getBlockState(pos.down()); +- return soil.getBlock().canSustainPlant(soil, worldIn, pos.down(), net.minecraft.util.EnumFacing.UP, this); ++ return soil.getBlock().canSustainPlant(soil, worldIn, pos.down(), EnumFacing.UP, this); + } + return this.canSustainBush(worldIn.getBlockState(pos.down())); + } diff --git a/patches/net/minecraft/block/BlockButton.java.patch b/patches/net/minecraft/block/BlockButton.java.patch new file mode 100644 index 00000000..b0b0aaeb --- /dev/null +++ b/patches/net/minecraft/block/BlockButton.java.patch @@ -0,0 +1,107 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockButton.java ++++ ../src-work/minecraft/net/minecraft/block/BlockButton.java +@@ -14,7 +14,6 @@ + import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.projectile.EntityArrow; +-import net.minecraft.init.Blocks; + import net.minecraft.util.EnumFacing; + import net.minecraft.util.EnumHand; + import net.minecraft.util.Mirror; +@@ -23,6 +22,8 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.event.block.BlockRedstoneEvent; ++import org.bukkit.event.entity.EntityInteractEvent; + + public abstract class BlockButton extends BlockDirectional + { +@@ -165,6 +166,17 @@ + } + else + { ++ boolean powered = ((Boolean) state.getValue(POWERED)); ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int old = (powered) ? 15 : 0; ++ int current = (!powered) ? 15 : 0; ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) { ++ return true; ++ } + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(true)), 3); + worldIn.markBlockRangeForRenderUpdate(pos, pos); + this.playClickSound(playerIn, worldIn, pos); +@@ -226,6 +238,14 @@ + } + else + { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() > 0) { ++ return; ++ } + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(false))); + this.notifyNeighbors(worldIn, pos, (EnumFacing)state.getValue(FACING)); + this.playReleaseSound(worldIn, pos); +@@ -255,8 +275,39 @@ + boolean flag = !list.isEmpty(); + boolean flag1 = ((Boolean)state.getValue(POWERED)).booleanValue(); + ++ // CraftBukkit start - Call interact event when arrows turn on wooden buttons ++ if (flag1 != flag && flag) { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ boolean allowed = false; ++ ++ // If all of the events are cancelled block the button press, else allow ++ for (Object object : list) { ++ if (object != null) { ++ EntityInteractEvent event = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ allowed = true; ++ break; ++ } ++ } ++ } ++ ++ if (!allowed) { ++ return; ++ } ++ } ++ + if (flag && !flag1) + { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 0, 15); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() <= 0) { ++ return; ++ } + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(true))); + this.notifyNeighbors(worldIn, pos, (EnumFacing)state.getValue(FACING)); + worldIn.markBlockRangeForRenderUpdate(pos, pos); +@@ -265,6 +316,14 @@ + + if (!flag && flag1) + { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() > 0) { ++ return; ++ } + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(false))); + this.notifyNeighbors(worldIn, pos, (EnumFacing)state.getValue(FACING)); + worldIn.markBlockRangeForRenderUpdate(pos, pos); diff --git a/patches/net/minecraft/block/BlockCactus.java.patch b/patches/net/minecraft/block/BlockCactus.java.patch new file mode 100644 index 00000000..fb9070b9 --- /dev/null +++ b/patches/net/minecraft/block/BlockCactus.java.patch @@ -0,0 +1,34 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCactus.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCactus.java +@@ -19,6 +19,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockCactus extends Block implements net.minecraftforge.common.IPlantable + { +@@ -54,10 +55,11 @@ + + if(net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, blockpos, state, true)) + { +- if (j == 15) ++ if (j >= (byte)Block.range(3.0f, 100.0f / worldIn.spigotConfig.cactusModifier * 15.0f + 0.5f, 15.0f)) // Spigot + { +- worldIn.setBlockState(blockpos, this.getDefaultState()); ++ // worldIn.setBlockState(blockpos, this.getDefaultState()); + IBlockState iblockstate = state.withProperty(AGE, Integer.valueOf(0)); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, blockpos.getX(), blockpos.getY(), blockpos.getZ(), this, 0); + worldIn.setBlockState(pos, iblockstate, 4); + iblockstate.neighborChanged(worldIn, blockpos, this, pos); + } +@@ -123,7 +125,9 @@ + + public void onEntityCollidedWithBlock(World worldIn, BlockPos pos, IBlockState state, Entity entityIn) + { ++ CraftEventFactory.blockDamage = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + entityIn.attackEntityFrom(DamageSource.CACTUS, 1.0F); ++ CraftEventFactory.blockDamage = null; + } + + public IBlockState getStateFromMeta(int meta) diff --git a/patches/net/minecraft/block/BlockCake.java.patch b/patches/net/minecraft/block/BlockCake.java.patch new file mode 100644 index 00000000..d168b176 --- /dev/null +++ b/patches/net/minecraft/block/BlockCake.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCake.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCake.java +@@ -8,6 +8,7 @@ + import net.minecraft.block.state.BlockStateContainer; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Items; + import net.minecraft.item.Item; + import net.minecraft.item.ItemStack; +@@ -71,7 +72,17 @@ + else + { + player.addStat(StatList.CAKE_SLICES_EATEN); ++ // Kettle Restore CraftBukkit changes for AppleCore recognition + player.getFoodStats().addStats(2, 0.1F); ++ int oldFoodLevel = player.getFoodStats().foodLevel; ++ ++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel); ++ ++ if (!event.isCancelled()) { ++ player.getFoodStats().addStats(event.getFoodLevel() - oldFoodLevel, 0.1F); ++ } ++ ++ ((EntityPlayerMP) player).getBukkitEntity().sendHealthUpdate(); + int i = ((Integer)state.getValue(BITES)).intValue(); + + if (i < 6) diff --git a/patches/net/minecraft/block/BlockCauldron.java.patch b/patches/net/minecraft/block/BlockCauldron.java.patch new file mode 100644 index 00000000..6a3b5c78 --- /dev/null +++ b/patches/net/minecraft/block/BlockCauldron.java.patch @@ -0,0 +1,171 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCauldron.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCauldron.java +@@ -31,6 +31,7 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.event.block.CauldronLevelChangeEvent; + + public class BlockCauldron extends Block + { +@@ -78,8 +79,11 @@ + + if (!worldIn.isRemote && entityIn.isBurning() && i > 0 && entityIn.getEntityBoundingBox().minY <= (double)f) + { ++ if (!this.changeLevel(worldIn, pos, state, i - 1, entityIn, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH)) { ++ return; ++ } + entityIn.extinguish(); +- this.setWaterLevel(worldIn, pos, state, i - 1); ++// this.setWaterLevel(worldIn, pos, state, i - 1); + } + } + +@@ -100,13 +104,16 @@ + { + if (i < 3 && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, 3, playerIn, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) { ++ return true; ++ } + if (!playerIn.capabilities.isCreativeMode) + { + playerIn.setHeldItem(hand, new ItemStack(Items.BUCKET)); + } + + playerIn.addStat(StatList.CAULDRON_FILLED); +- this.setWaterLevel(worldIn, pos, state, 3); ++// this.setWaterLevel(worldIn, pos, state, 3); + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.ITEM_BUCKET_EMPTY, SoundCategory.BLOCKS, 1.0F, 1.0F); + } + +@@ -116,6 +123,9 @@ + { + if (i == 3 && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, 0, playerIn, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) { ++ return true; ++ } + if (!playerIn.capabilities.isCreativeMode) + { + itemstack.shrink(1); +@@ -131,7 +141,7 @@ + } + + playerIn.addStat(StatList.CAULDRON_USED); +- this.setWaterLevel(worldIn, pos, state, 0); ++// this.setWaterLevel(worldIn, pos, state, 0); + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.ITEM_BUCKET_FILL, SoundCategory.BLOCKS, 1.0F, 1.0F); + } + +@@ -141,6 +151,9 @@ + { + if (i > 0 && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, i - 1, playerIn, CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) { ++ return true; ++ } + if (!playerIn.capabilities.isCreativeMode) + { + ItemStack itemstack3 = PotionUtils.addPotionToItemStack(new ItemStack(Items.POTIONITEM), PotionTypes.WATER); +@@ -162,7 +175,7 @@ + } + + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.ITEM_BOTTLE_FILL, SoundCategory.BLOCKS, 1.0F, 1.0F); +- this.setWaterLevel(worldIn, pos, state, i - 1); ++// this.setWaterLevel(worldIn, pos, state, i - 1); + } + + return true; +@@ -171,6 +184,9 @@ + { + if (i < 3 && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, i + 1, playerIn, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) { ++ return true; ++ } + if (!playerIn.capabilities.isCreativeMode) + { + ItemStack itemstack2 = new ItemStack(Items.GLASS_BOTTLE); +@@ -184,7 +200,7 @@ + } + + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.ITEM_BOTTLE_EMPTY, SoundCategory.BLOCKS, 1.0F, 1.0F); +- this.setWaterLevel(worldIn, pos, state, i + 1); ++// this.setWaterLevel(worldIn, pos, state, i + 1); + } + + return true; +@@ -197,8 +213,11 @@ + + if (itemarmor.getArmorMaterial() == ItemArmor.ArmorMaterial.LEATHER && itemarmor.hasColor(itemstack) && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, i - 1, playerIn, CauldronLevelChangeEvent.ChangeReason.ARMOR_WASH)) { ++ return true; ++ } + itemarmor.removeColor(itemstack); +- this.setWaterLevel(worldIn, pos, state, i - 1); ++// this.setWaterLevel(worldIn, pos, state, i - 1); + playerIn.addStat(StatList.ARMOR_CLEANED); + return true; + } +@@ -208,6 +227,9 @@ + { + if (TileEntityBanner.getPatterns(itemstack) > 0 && !worldIn.isRemote) + { ++ if (!this.changeLevel(worldIn, pos, state, i - 1, playerIn, CauldronLevelChangeEvent.ChangeReason.BANNER_WASH)) { ++ return true; ++ } + ItemStack itemstack1 = itemstack.copy(); + itemstack1.setCount(1); + TileEntityBanner.removeBannerData(itemstack1); +@@ -216,7 +238,7 @@ + if (!playerIn.capabilities.isCreativeMode) + { + itemstack.shrink(1); +- this.setWaterLevel(worldIn, pos, state, i - 1); ++// this.setWaterLevel(worldIn, pos, state, i - 1); + } + + if (itemstack.isEmpty()) +@@ -245,10 +267,29 @@ + + public void setWaterLevel(World worldIn, BlockPos pos, IBlockState state, int level) + { +- worldIn.setBlockState(pos, state.withProperty(LEVEL, Integer.valueOf(MathHelper.clamp(level, 0, 3))), 2); +- worldIn.updateComparatorOutputLevel(pos, this); ++ this.changeLevel(worldIn, pos, state, level, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN); + } + ++ public void changeLevel(World world, BlockPos blockposition, IBlockState iblockdata, int i) { ++ this.changeLevel(world, blockposition, iblockdata, i, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN); ++ } ++ ++ private boolean changeLevel(World world, BlockPos blockposition, IBlockState iblockdata, int i, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { ++ int newLevel = Integer.valueOf(MathHelper.clamp(i, 0, 3)); ++ CauldronLevelChangeEvent event = new CauldronLevelChangeEvent( ++ world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), ++ (entity == null) ? null : entity.getBukkitEntity(), reason, iblockdata.getValue(BlockCauldron.LEVEL), newLevel ++ ); ++ world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ world.setBlockState(blockposition, iblockdata.withProperty(BlockCauldron.LEVEL, event.getNewLevel()), 2); ++ world.updateComparatorOutputLevel(blockposition, this); ++ world.updateComparatorOutputLevel(blockposition, this); ++ return true; ++ } ++ + public void fillWithRain(World worldIn, BlockPos pos) + { + if (worldIn.rand.nextInt(20) == 1) +@@ -261,7 +302,7 @@ + + if (((Integer)iblockstate.getValue(LEVEL)).intValue() < 3) + { +- worldIn.setBlockState(pos, iblockstate.cycleProperty(LEVEL), 2); ++ this.changeLevel(worldIn, pos, iblockstate.cycleProperty(LEVEL), 2); + } + } + } diff --git a/patches/net/minecraft/block/BlockChorusFlower.java.patch b/patches/net/minecraft/block/BlockChorusFlower.java.patch new file mode 100644 index 00000000..b610b1c4 --- /dev/null +++ b/patches/net/minecraft/block/BlockChorusFlower.java.patch @@ -0,0 +1,85 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockChorusFlower.java ++++ ../src-work/minecraft/net/minecraft/block/BlockChorusFlower.java +@@ -23,6 +23,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockChorusFlower extends Block + { +@@ -106,8 +107,18 @@ + + if (flag && areAllNeighborsEmpty(worldIn, blockpos, (EnumFacing)null) && worldIn.isAirBlock(pos.up(2))) + { +- worldIn.setBlockState(pos, Blocks.CHORUS_PLANT.getDefaultState(), 2); +- this.placeGrownFlower(worldIn, blockpos, i); ++// worldIn.setBlockState(pos, Blocks.CHORUS_PLANT.getDefaultState(), 2); ++// this.placeGrownFlower(worldIn, blockpos, i); ++ BlockPos target = blockpos; ++ if (CraftEventFactory.handleBlockSpreadEvent( ++ worldIn.getWorld().getBlockAt(target.getX(), target.getY(), target.getZ()), ++ worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), ++ this, ++ getMetaFromState(this.getDefaultState().withProperty(BlockChorusFlower.AGE, Integer.valueOf(i))) ++ )) { ++ worldIn.setBlockState(pos, Blocks.CHORUS_PLANT.getDefaultState(), 2); ++ worldIn.playEvent(1033, pos, 0); ++ } + } + else if (i < 4) + { +@@ -126,7 +137,17 @@ + + if (worldIn.isAirBlock(blockpos1) && worldIn.isAirBlock(blockpos1.down()) && areAllNeighborsEmpty(worldIn, blockpos1, enumfacing.getOpposite())) + { +- this.placeGrownFlower(worldIn, blockpos1, i + 1); ++// this.placeGrownFlower(worldIn, blockpos1, i + 1); ++ BlockPos target = blockpos1; ++ if (CraftEventFactory.handleBlockSpreadEvent( ++ worldIn.getWorld().getBlockAt(target.getX(), target.getY(), target.getZ()), ++ worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), ++ this, ++ getMetaFromState(this.getDefaultState().withProperty(BlockChorusFlower.AGE, Integer.valueOf(i + 1))) ++ )) { ++ worldIn.playEvent(1033, pos, 0); ++ flag2 = true; ++ } + flag2 = true; + } + } +@@ -137,12 +158,32 @@ + } + else + { +- this.placeDeadFlower(worldIn, pos); ++// this.placeDeadFlower(worldIn, pos); ++ if (CraftEventFactory.handleBlockGrowEvent( ++ worldIn, ++ pos.getX(), ++ pos.getY(), ++ pos.getZ(), ++ this, ++ getMetaFromState(state.withProperty(BlockChorusFlower.AGE, Integer.valueOf(5))) ++ )) { ++ worldIn.playEvent(1034, pos, 0); ++ } + } + } + else if (i == 4) + { +- this.placeDeadFlower(worldIn, pos); ++// this.placeDeadFlower(worldIn, pos); ++ if (CraftEventFactory.handleBlockGrowEvent( ++ worldIn, ++ pos.getX(), ++ pos.getY(), ++ pos.getZ(), ++ this, ++ getMetaFromState(state.withProperty(BlockChorusFlower.AGE, Integer.valueOf(5))) ++ )) { ++ worldIn.playEvent(1034, pos, 0); ++ } + } + net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos)); + } diff --git a/patches/net/minecraft/block/BlockCocoa.java.patch b/patches/net/minecraft/block/BlockCocoa.java.patch new file mode 100644 index 00000000..360cec67 --- /dev/null +++ b/patches/net/minecraft/block/BlockCocoa.java.patch @@ -0,0 +1,33 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCocoa.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCocoa.java +@@ -22,6 +22,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockCocoa extends BlockHorizontal implements IGrowable + { +@@ -48,9 +49,10 @@ + { + int i = ((Integer)state.getValue(AGE)).intValue(); + +- if (i < 2 && net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, rand.nextInt(5) == 0)) ++ if (i < 2 && net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, rand.nextInt(Math.max(1, (int)(100.0f / worldIn.spigotConfig.cocoaModifier) * 5)) == 0)) // Spigot + { +- worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(i + 1)), 2); ++ IBlockState data = state.withProperty(AGE, i + 1); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(data)); + net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos)); + } + } +@@ -171,7 +173,8 @@ + + public void grow(World worldIn, Random rand, BlockPos pos, IBlockState state) + { +- worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(((Integer)state.getValue(AGE)).intValue() + 1)), 2); ++ IBlockState data = state.withProperty(AGE, state.getValue(AGE) + 1); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(data)); + } + + @SideOnly(Side.CLIENT) diff --git a/patches/net/minecraft/block/BlockCommandBlock.java.patch b/patches/net/minecraft/block/BlockCommandBlock.java.patch new file mode 100644 index 00000000..43538ae7 --- /dev/null +++ b/patches/net/minecraft/block/BlockCommandBlock.java.patch @@ -0,0 +1,24 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCommandBlock.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCommandBlock.java +@@ -27,6 +27,7 @@ + import net.minecraft.world.World; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockCommandBlock extends BlockContainer + { +@@ -58,6 +59,13 @@ + TileEntityCommandBlock tileentitycommandblock = (TileEntityCommandBlock)tileentity; + boolean flag = worldIn.isBlockPowered(pos); + boolean flag1 = tileentitycommandblock.isPowered(); ++ org.bukkit.block.Block bukkitBlock = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int old = flag1 ? 15 : 0; ++ int current = flag ? 15 : 0; ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, old, current); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ flag = eventRedstone.getNewCurrent() > 0; + tileentitycommandblock.setPowered(flag); + + if (!flag1 && !tileentitycommandblock.isAuto() && tileentitycommandblock.getMode() != TileEntityCommandBlock.Mode.SEQUENCE) diff --git a/patches/net/minecraft/block/BlockConcretePowder.java.patch b/patches/net/minecraft/block/BlockConcretePowder.java.patch new file mode 100644 index 00000000..bdd3d9c3 --- /dev/null +++ b/patches/net/minecraft/block/BlockConcretePowder.java.patch @@ -0,0 +1,23 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockConcretePowder.java ++++ ../src-work/minecraft/net/minecraft/block/BlockConcretePowder.java +@@ -29,9 +29,8 @@ + + public void onEndFalling(World worldIn, BlockPos pos, IBlockState p_176502_3_, IBlockState p_176502_4_) + { +- if (p_176502_4_.getMaterial().isLiquid()) +- { +- worldIn.setBlockState(pos, Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, p_176502_3_.getValue(COLOR)), 3); ++ if (p_176502_4_.getMaterial().isLiquid() && worldIn.getBlockState(pos).getBlock() != Blocks.CONCRETE) { // CraftBukkit - don't double concrete ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, pos, Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, p_176502_3_.getValue(BlockConcretePowder.COLOR)), null); // CraftBukkit + } + } + +@@ -55,7 +54,7 @@ + + if (flag) + { +- worldIn.setBlockState(pos, Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, state.getValue(COLOR)), 3); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, pos, Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, state.getValue(BlockConcretePowder.COLOR)), null); + } + + return flag; diff --git a/patches/net/minecraft/block/BlockCrops.java.patch b/patches/net/minecraft/block/BlockCrops.java.patch new file mode 100644 index 00000000..a6cfdd52 --- /dev/null +++ b/patches/net/minecraft/block/BlockCrops.java.patch @@ -0,0 +1,30 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockCrops.java ++++ ../src-work/minecraft/net/minecraft/block/BlockCrops.java +@@ -15,6 +15,7 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockCrops extends BlockBush implements IGrowable + { +@@ -81,7 +82,8 @@ + + if(net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, rand.nextInt((int)(25.0F / f) + 1) == 0)) + { +- worldIn.setBlockState(pos, this.withAge(i + 1), 2); ++ IBlockState data = this.withAge(i + 1); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(data)); + net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos)); + } + } +@@ -98,7 +100,8 @@ + i = j; + } + +- worldIn.setBlockState(pos, this.withAge(i), 2); ++ IBlockState data = this.withAge(i); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(data)); + } + + protected int getBonemealAgeIncrease(World worldIn) diff --git a/patches/net/minecraft/block/BlockDaylightDetector.java.patch b/patches/net/minecraft/block/BlockDaylightDetector.java.patch new file mode 100644 index 00000000..28facfca --- /dev/null +++ b/patches/net/minecraft/block/BlockDaylightDetector.java.patch @@ -0,0 +1,10 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDaylightDetector.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDaylightDetector.java +@@ -76,6 +76,7 @@ + + if (((Integer)iblockstate.getValue(POWER)).intValue() != i) + { ++ i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), iblockstate.getValue(POWER), i).getNewCurrent(); + worldIn.setBlockState(pos, iblockstate.withProperty(POWER, Integer.valueOf(i)), 3); + } + } diff --git a/patches/net/minecraft/block/BlockDispenser.java.patch b/patches/net/minecraft/block/BlockDispenser.java.patch new file mode 100644 index 00000000..77fcec6a --- /dev/null +++ b/patches/net/minecraft/block/BlockDispenser.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDispenser.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDispenser.java +@@ -120,7 +120,7 @@ + } + } + +- protected void dispense(World worldIn, BlockPos pos) ++ public void dispense(World worldIn, BlockPos pos) + { + BlockSourceImpl blocksourceimpl = new BlockSourceImpl(worldIn, pos); + TileEntityDispenser tileentitydispenser = (TileEntityDispenser)blocksourceimpl.getBlockTileEntity(); diff --git a/patches/net/minecraft/block/BlockDoor.java.patch b/patches/net/minecraft/block/BlockDoor.java.patch new file mode 100644 index 00000000..c6ad3a7b --- /dev/null +++ b/patches/net/minecraft/block/BlockDoor.java.patch @@ -0,0 +1,164 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDoor.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDoor.java +@@ -29,14 +29,15 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockDoor extends Block + { + public static final PropertyDirection FACING = BlockHorizontal.FACING; + public static final PropertyBool OPEN = PropertyBool.create("open"); +- public static final PropertyEnum HINGE = PropertyEnum.create("hinge", BlockDoor.EnumHingePosition.class); ++ public static final PropertyEnum HINGE = PropertyEnum.create("hinge", EnumHingePosition.class); + public static final PropertyBool POWERED = PropertyBool.create("powered"); +- public static final PropertyEnum HALF = PropertyEnum.create("half", BlockDoor.EnumDoorHalf.class); ++ public static final PropertyEnum HALF = PropertyEnum.create("half", EnumDoorHalf.class); + protected static final AxisAlignedBB SOUTH_AABB = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D, 0.1875D); + protected static final AxisAlignedBB NORTH_AABB = new AxisAlignedBB(0.0D, 0.0D, 0.8125D, 1.0D, 1.0D, 1.0D); + protected static final AxisAlignedBB WEST_AABB = new AxisAlignedBB(0.8125D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D); +@@ -45,7 +46,7 @@ + protected BlockDoor(Material materialIn) + { + super(materialIn); +- this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH).withProperty(OPEN, Boolean.valueOf(false)).withProperty(HINGE, BlockDoor.EnumHingePosition.LEFT).withProperty(POWERED, Boolean.valueOf(false)).withProperty(HALF, BlockDoor.EnumDoorHalf.LOWER)); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH).withProperty(OPEN, Boolean.valueOf(false)).withProperty(HINGE, EnumHingePosition.LEFT).withProperty(POWERED, Boolean.valueOf(false)).withProperty(HALF, EnumDoorHalf.LOWER)); + } + + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) +@@ -53,7 +54,7 @@ + state = state.getActualState(source, pos); + EnumFacing enumfacing = (EnumFacing)state.getValue(FACING); + boolean flag = !((Boolean)state.getValue(OPEN)).booleanValue(); +- boolean flag1 = state.getValue(HINGE) == BlockDoor.EnumHingePosition.RIGHT; ++ boolean flag1 = state.getValue(HINGE) == EnumHingePosition.RIGHT; + + switch (enumfacing) + { +@@ -139,7 +140,7 @@ + } + else + { +- BlockPos blockpos = state.getValue(HALF) == BlockDoor.EnumDoorHalf.LOWER ? pos : pos.down(); ++ BlockPos blockpos = state.getValue(HALF) == EnumDoorHalf.LOWER ? pos : pos.down(); + IBlockState iblockstate = pos.equals(blockpos) ? state : worldIn.getBlockState(blockpos); + + if (iblockstate.getBlock() != this) +@@ -163,7 +164,7 @@ + + if (iblockstate.getBlock() == this) + { +- BlockPos blockpos = iblockstate.getValue(HALF) == BlockDoor.EnumDoorHalf.LOWER ? pos : pos.down(); ++ BlockPos blockpos = iblockstate.getValue(HALF) == EnumDoorHalf.LOWER ? pos : pos.down(); + IBlockState iblockstate1 = pos == blockpos ? iblockstate : worldIn.getBlockState(blockpos); + + if (iblockstate1.getBlock() == this && ((Boolean)iblockstate1.getValue(OPEN)).booleanValue() != open) +@@ -177,7 +178,7 @@ + + public void neighborChanged(IBlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos) + { +- if (state.getValue(HALF) == BlockDoor.EnumDoorHalf.UPPER) ++ if (state.getValue(HALF) == EnumDoorHalf.UPPER) + { + BlockPos blockpos = pos.down(); + IBlockState iblockstate = worldIn.getBlockState(blockpos); +@@ -223,17 +224,27 @@ + } + else + { +- boolean flag = worldIn.isBlockPowered(pos) || worldIn.isBlockPowered(blockpos1); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block blockTop = bworld.getBlockAt(blockpos1.getX(), blockpos1.getY(), blockpos1.getZ()); + +- if (blockIn != this && (flag || blockIn.getDefaultState().canProvidePower()) && flag != ((Boolean)iblockstate1.getValue(POWERED)).booleanValue()) +- { +- worldIn.setBlockState(blockpos1, iblockstate1.withProperty(POWERED, Boolean.valueOf(flag)), 2); ++ int power = bukkitBlock.getBlockPower(); ++ int powerTop = blockTop.getBlockPower(); ++ if (powerTop > power) power = powerTop; ++ int oldPower = iblockstate1.getValue(BlockDoor.POWERED) ? 15 : 0; + +- if (flag != ((Boolean)state.getValue(OPEN)).booleanValue()) ++ if (oldPower == 0 ^ power == 0) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, oldPower, power); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ boolean flag2 = eventRedstone.getNewCurrent() > 0; ++ worldIn.setBlockState(blockpos1, iblockstate1.withProperty(POWERED, Boolean.valueOf(flag2)), 2); ++ ++ if (flag2 != state.getValue(OPEN)) + { +- worldIn.setBlockState(pos, state.withProperty(OPEN, Boolean.valueOf(flag)), 2); ++ worldIn.setBlockState(pos, state.withProperty(OPEN, Boolean.valueOf(flag2)), 2); + worldIn.markBlockRangeForRenderUpdate(pos, pos); +- worldIn.playEvent((EntityPlayer)null, flag ? this.getOpenSound() : this.getCloseSound(), pos, 0); ++ worldIn.playEvent(null, flag2 ? this.getOpenSound() : this.getCloseSound(), pos, 0); + } + } + } +@@ -242,7 +253,7 @@ + + public Item getItemDropped(IBlockState state, Random rand, int fortune) + { +- return state.getValue(HALF) == BlockDoor.EnumDoorHalf.UPPER ? Items.AIR : this.getItem(); ++ return state.getValue(HALF) == EnumDoorHalf.UPPER ? Items.AIR : this.getItem(); + } + + public boolean canPlaceBlockAt(World worldIn, BlockPos pos) +@@ -317,12 +328,12 @@ + BlockPos blockpos = pos.down(); + BlockPos blockpos1 = pos.up(); + +- if (player.capabilities.isCreativeMode && state.getValue(HALF) == BlockDoor.EnumDoorHalf.UPPER && worldIn.getBlockState(blockpos).getBlock() == this) ++ if (player.capabilities.isCreativeMode && state.getValue(HALF) == EnumDoorHalf.UPPER && worldIn.getBlockState(blockpos).getBlock() == this) + { + worldIn.setBlockToAir(blockpos); + } + +- if (state.getValue(HALF) == BlockDoor.EnumDoorHalf.LOWER && worldIn.getBlockState(blockpos1).getBlock() == this) ++ if (state.getValue(HALF) == EnumDoorHalf.LOWER && worldIn.getBlockState(blockpos1).getBlock() == this) + { + if (player.capabilities.isCreativeMode) + { +@@ -341,7 +352,7 @@ + + public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos) + { +- if (state.getValue(HALF) == BlockDoor.EnumDoorHalf.LOWER) ++ if (state.getValue(HALF) == EnumDoorHalf.LOWER) + { + IBlockState iblockstate = worldIn.getBlockState(pos.up()); + +@@ -365,7 +376,7 @@ + + public IBlockState withRotation(IBlockState state, Rotation rot) + { +- return state.getValue(HALF) != BlockDoor.EnumDoorHalf.LOWER ? state : state.withProperty(FACING, rot.rotate((EnumFacing)state.getValue(FACING))); ++ return state.getValue(HALF) != EnumDoorHalf.LOWER ? state : state.withProperty(FACING, rot.rotate((EnumFacing)state.getValue(FACING))); + } + + public IBlockState withMirror(IBlockState state, Mirror mirrorIn) +@@ -375,18 +386,18 @@ + + public IBlockState getStateFromMeta(int meta) + { +- return (meta & 8) > 0 ? this.getDefaultState().withProperty(HALF, BlockDoor.EnumDoorHalf.UPPER).withProperty(HINGE, (meta & 1) > 0 ? BlockDoor.EnumHingePosition.RIGHT : BlockDoor.EnumHingePosition.LEFT).withProperty(POWERED, Boolean.valueOf((meta & 2) > 0)) : this.getDefaultState().withProperty(HALF, BlockDoor.EnumDoorHalf.LOWER).withProperty(FACING, EnumFacing.getHorizontal(meta & 3).rotateYCCW()).withProperty(OPEN, Boolean.valueOf((meta & 4) > 0)); ++ return (meta & 8) > 0 ? this.getDefaultState().withProperty(HALF, EnumDoorHalf.UPPER).withProperty(HINGE, (meta & 1) > 0 ? EnumHingePosition.RIGHT : EnumHingePosition.LEFT).withProperty(POWERED, Boolean.valueOf((meta & 2) > 0)) : this.getDefaultState().withProperty(HALF, EnumDoorHalf.LOWER).withProperty(FACING, EnumFacing.getHorizontal(meta & 3).rotateYCCW()).withProperty(OPEN, Boolean.valueOf((meta & 4) > 0)); + } + + public int getMetaFromState(IBlockState state) + { + int i = 0; + +- if (state.getValue(HALF) == BlockDoor.EnumDoorHalf.UPPER) ++ if (state.getValue(HALF) == EnumDoorHalf.UPPER) + { + i = i | 8; + +- if (state.getValue(HINGE) == BlockDoor.EnumHingePosition.RIGHT) ++ if (state.getValue(HINGE) == EnumHingePosition.RIGHT) + { + i |= 1; + } diff --git a/patches/net/minecraft/block/BlockDoublePlant.java.patch b/patches/net/minecraft/block/BlockDoublePlant.java.patch new file mode 100644 index 00000000..0748a837 --- /dev/null +++ b/patches/net/minecraft/block/BlockDoublePlant.java.patch @@ -0,0 +1,240 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDoublePlant.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDoublePlant.java +@@ -24,16 +24,18 @@ + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; + ++import net.minecraft.block.Block.EnumOffsetType; ++ + public class BlockDoublePlant extends BlockBush implements IGrowable, net.minecraftforge.common.IShearable + { +- public static final PropertyEnum VARIANT = PropertyEnum.create("variant", BlockDoublePlant.EnumPlantType.class); +- public static final PropertyEnum HALF = PropertyEnum.create("half", BlockDoublePlant.EnumBlockHalf.class); ++ public static final PropertyEnum VARIANT = PropertyEnum.create("variant", EnumPlantType.class); ++ public static final PropertyEnum HALF = PropertyEnum.create("half", EnumBlockHalf.class); + public static final PropertyEnum FACING = BlockHorizontal.FACING; + + public BlockDoublePlant() + { + super(Material.VINE); +- this.setDefaultState(this.blockState.getBaseState().withProperty(VARIANT, BlockDoublePlant.EnumPlantType.SUNFLOWER).withProperty(HALF, BlockDoublePlant.EnumBlockHalf.LOWER).withProperty(FACING, EnumFacing.NORTH)); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(VARIANT, EnumPlantType.SUNFLOWER).withProperty(HALF, EnumBlockHalf.LOWER).withProperty(FACING, EnumFacing.NORTH)); + this.setHardness(0.0F); + this.setSoundType(SoundType.PLANT); + this.setUnlocalizedName("doublePlant"); +@@ -44,16 +46,16 @@ + return FULL_BLOCK_AABB; + } + +- private BlockDoublePlant.EnumPlantType getType(IBlockAccess blockAccess, BlockPos pos, IBlockState state) ++ private EnumPlantType getType(IBlockAccess blockAccess, BlockPos pos, IBlockState state) + { + if (state.getBlock() == this) + { + state = state.getActualState(blockAccess, pos); +- return (BlockDoublePlant.EnumPlantType)state.getValue(VARIANT); ++ return (EnumPlantType)state.getValue(VARIANT); + } + else + { +- return BlockDoublePlant.EnumPlantType.FERN; ++ return EnumPlantType.FERN; + } + } + +@@ -72,8 +74,8 @@ + } + else + { +- BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype = (BlockDoublePlant.EnumPlantType)iblockstate.getActualState(worldIn, pos).getValue(VARIANT); +- return blockdoubleplant$enumplanttype == BlockDoublePlant.EnumPlantType.FERN || blockdoubleplant$enumplanttype == BlockDoublePlant.EnumPlantType.GRASS; ++ EnumPlantType blockdoubleplant$enumplanttype = (EnumPlantType)iblockstate.getActualState(worldIn, pos).getValue(VARIANT); ++ return blockdoubleplant$enumplanttype == EnumPlantType.FERN || blockdoubleplant$enumplanttype == EnumPlantType.GRASS; + } + } + +@@ -81,7 +83,10 @@ + { + if (!this.canBlockStay(worldIn, pos, state)) + { +- boolean flag = state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER; ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(worldIn, pos).isCancelled()) { ++ return; ++ } ++ boolean flag = state.getValue(HALF) == EnumBlockHalf.UPPER; + BlockPos blockpos = flag ? pos : pos.up(); + BlockPos blockpos1 = flag ? pos.down() : pos; + Block block = (Block)(flag ? this : worldIn.getBlockState(blockpos).getBlock()); +@@ -104,7 +109,7 @@ + public boolean canBlockStay(World worldIn, BlockPos pos, IBlockState state) + { + if (state.getBlock() != this) return super.canBlockStay(worldIn, pos, state); //Forge: This function is called during world gen and placement, before this block is set, so if we are not 'here' then assume it's the pre-check. +- if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER) ++ if (state.getValue(HALF) == EnumBlockHalf.UPPER) + { + return worldIn.getBlockState(pos.down()).getBlock() == this; + } +@@ -117,19 +122,19 @@ + + public Item getItemDropped(IBlockState state, Random rand, int fortune) + { +- if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER) ++ if (state.getValue(HALF) == EnumBlockHalf.UPPER) + { + return Items.AIR; + } + else + { +- BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype = (BlockDoublePlant.EnumPlantType)state.getValue(VARIANT); ++ EnumPlantType blockdoubleplant$enumplanttype = (EnumPlantType)state.getValue(VARIANT); + +- if (blockdoubleplant$enumplanttype == BlockDoublePlant.EnumPlantType.FERN) ++ if (blockdoubleplant$enumplanttype == EnumPlantType.FERN) + { + return Items.AIR; + } +- else if (blockdoubleplant$enumplanttype == BlockDoublePlant.EnumPlantType.GRASS) ++ else if (blockdoubleplant$enumplanttype == EnumPlantType.GRASS) + { + return rand.nextInt(8) == 0 ? Items.WHEAT_SEEDS : Items.AIR; + } +@@ -142,18 +147,18 @@ + + public int damageDropped(IBlockState state) + { +- return state.getValue(HALF) != BlockDoublePlant.EnumBlockHalf.UPPER && state.getValue(VARIANT) != BlockDoublePlant.EnumPlantType.GRASS ? ((BlockDoublePlant.EnumPlantType)state.getValue(VARIANT)).getMeta() : 0; ++ return state.getValue(HALF) != EnumBlockHalf.UPPER && state.getValue(VARIANT) != EnumPlantType.GRASS ? ((EnumPlantType)state.getValue(VARIANT)).getMeta() : 0; + } + +- public void placeAt(World worldIn, BlockPos lowerPos, BlockDoublePlant.EnumPlantType variant, int flags) ++ public void placeAt(World worldIn, BlockPos lowerPos, EnumPlantType variant, int flags) + { +- worldIn.setBlockState(lowerPos, this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.LOWER).withProperty(VARIANT, variant), flags); +- worldIn.setBlockState(lowerPos.up(), this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER), flags); ++ worldIn.setBlockState(lowerPos, this.getDefaultState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(VARIANT, variant), flags); ++ worldIn.setBlockState(lowerPos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER), flags); + } + + public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) + { +- worldIn.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER), 2); ++ worldIn.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER), 2); + } + + public void harvestBlock(World worldIn, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack stack) +@@ -165,7 +170,7 @@ + + public void onBlockHarvested(World worldIn, BlockPos pos, IBlockState state, EntityPlayer player) + { +- if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER) ++ if (state.getValue(HALF) == EnumBlockHalf.UPPER) + { + if (worldIn.getBlockState(pos.down()).getBlock() == this) + { +@@ -176,9 +181,9 @@ + else + { + IBlockState iblockstate = worldIn.getBlockState(pos.down()); +- BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype = (BlockDoublePlant.EnumPlantType)iblockstate.getValue(VARIANT); ++ EnumPlantType blockdoubleplant$enumplanttype = (EnumPlantType)iblockstate.getValue(VARIANT); + +- if (blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.FERN && blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.GRASS) ++ if (blockdoubleplant$enumplanttype != EnumPlantType.FERN && blockdoubleplant$enumplanttype != EnumPlantType.GRASS) + { + worldIn.destroyBlock(pos.down(), true); + } +@@ -208,9 +213,9 @@ + + private boolean onHarvest(World worldIn, BlockPos pos, IBlockState state, EntityPlayer player) + { +- BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype = (BlockDoublePlant.EnumPlantType)state.getValue(VARIANT); ++ EnumPlantType blockdoubleplant$enumplanttype = (EnumPlantType)state.getValue(VARIANT); + +- if (blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.FERN && blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.GRASS) ++ if (blockdoubleplant$enumplanttype != EnumPlantType.FERN && blockdoubleplant$enumplanttype != EnumPlantType.GRASS) + { + return false; + } +@@ -223,7 +228,7 @@ + + public void getSubBlocks(CreativeTabs itemIn, NonNullList items) + { +- for (BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype : BlockDoublePlant.EnumPlantType.values()) ++ for (EnumPlantType blockdoubleplant$enumplanttype : EnumPlantType.values()) + { + items.add(new ItemStack(this, 1, blockdoubleplant$enumplanttype.getMeta())); + } +@@ -236,8 +241,8 @@ + + public boolean canGrow(World worldIn, BlockPos pos, IBlockState state, boolean isClient) + { +- BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype = this.getType(worldIn, pos, state); +- return blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.GRASS && blockdoubleplant$enumplanttype != BlockDoublePlant.EnumPlantType.FERN; ++ EnumPlantType blockdoubleplant$enumplanttype = this.getType(worldIn, pos, state); ++ return blockdoubleplant$enumplanttype != EnumPlantType.GRASS && blockdoubleplant$enumplanttype != EnumPlantType.FERN; + } + + public boolean canUseBonemeal(World worldIn, Random rand, BlockPos pos, IBlockState state) +@@ -252,12 +257,12 @@ + + public IBlockState getStateFromMeta(int meta) + { +- return (meta & 8) > 0 ? this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER) : this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.LOWER).withProperty(VARIANT, BlockDoublePlant.EnumPlantType.byMetadata(meta & 7)); ++ return (meta & 8) > 0 ? this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER) : this.getDefaultState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(VARIANT, EnumPlantType.byMetadata(meta & 7)); + } + + public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos) + { +- if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER) ++ if (state.getValue(HALF) == EnumBlockHalf.UPPER) + { + IBlockState iblockstate = worldIn.getBlockState(pos.down()); + +@@ -272,7 +277,7 @@ + + public int getMetaFromState(IBlockState state) + { +- return state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER ? 8 | ((EnumFacing)state.getValue(FACING)).getHorizontalIndex() : ((BlockDoublePlant.EnumPlantType)state.getValue(VARIANT)).getMeta(); ++ return state.getValue(HALF) == EnumBlockHalf.UPPER ? 8 | ((EnumFacing)state.getValue(FACING)).getHorizontalIndex() : ((EnumPlantType)state.getValue(VARIANT)).getMeta(); + } + + protected BlockStateContainer createBlockState() +@@ -280,9 +285,9 @@ + return new BlockStateContainer(this, new IProperty[] {HALF, VARIANT, FACING}); + } + +- public Block.EnumOffsetType getOffsetType() ++ public EnumOffsetType getOffsetType() + { +- return Block.EnumOffsetType.XZ; ++ return EnumOffsetType.XZ; + } + + @Override +@@ -328,7 +333,7 @@ + ROSE(4, "double_rose", "rose"), + PAEONIA(5, "paeonia"); + +- private static final BlockDoublePlant.EnumPlantType[] META_LOOKUP = new BlockDoublePlant.EnumPlantType[values().length]; ++ private static final EnumPlantType[] META_LOOKUP = new EnumPlantType[values().length]; + private final int meta; + private final String name; + private final String unlocalizedName; +@@ -355,7 +360,7 @@ + return this.name; + } + +- public static BlockDoublePlant.EnumPlantType byMetadata(int meta) ++ public static EnumPlantType byMetadata(int meta) + { + if (meta < 0 || meta >= META_LOOKUP.length) + { +@@ -377,7 +382,7 @@ + + static + { +- for (BlockDoublePlant.EnumPlantType blockdoubleplant$enumplanttype : values()) ++ for (EnumPlantType blockdoubleplant$enumplanttype : values()) + { + META_LOOKUP[blockdoubleplant$enumplanttype.getMeta()] = blockdoubleplant$enumplanttype; + } diff --git a/patches/net/minecraft/block/BlockDragonEgg.java.patch b/patches/net/minecraft/block/BlockDragonEgg.java.patch new file mode 100644 index 00000000..98f28f7d --- /dev/null +++ b/patches/net/minecraft/block/BlockDragonEgg.java.patch @@ -0,0 +1,36 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDragonEgg.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDragonEgg.java +@@ -16,6 +16,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockFromToEvent; + + public class BlockDragonEgg extends Block + { +@@ -68,7 +69,7 @@ + + if (blockpos.getY() > 0) + { +- worldIn.setBlockState(blockpos, this.getDefaultState(), 2); ++ worldIn.setBlockState(blockpos.up(), this.getDefaultState(), 2); // Paper MC-94186 Fix dragon egg falling in lazy chunks + } + } + } +@@ -97,6 +98,16 @@ + + if (worldIn.isAirBlock(blockpos)) + { ++ org.bukkit.block.Block from = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block to = worldIn.getWorld().getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()); ++ BlockFromToEvent event = new BlockFromToEvent(from, to); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ blockpos = new BlockPos(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ()); + if (worldIn.isRemote) + { + for (int j = 0; j < 128; ++j) diff --git a/patches/net/minecraft/block/BlockDropper.java.patch b/patches/net/minecraft/block/BlockDropper.java.patch new file mode 100644 index 00000000..217ad4ad --- /dev/null +++ b/patches/net/minecraft/block/BlockDropper.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDropper.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDropper.java +@@ -3,6 +3,7 @@ + import net.minecraft.dispenser.BehaviorDefaultDispenseItem; + import net.minecraft.dispenser.IBehaviorDispenseItem; + import net.minecraft.inventory.IInventory; ++import net.minecraft.inventory.InventoryLargeChest; + import net.minecraft.item.ItemStack; + import net.minecraft.tileentity.TileEntity; + import net.minecraft.tileentity.TileEntityDispenser; +@@ -11,6 +12,8 @@ + import net.minecraft.util.EnumFacing; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.inventory.InventoryMoveItemEvent; + + public class BlockDropper extends BlockDispenser + { +@@ -26,7 +29,7 @@ + return new TileEntityDropper(); + } + +- protected void dispense(World worldIn, BlockPos pos) ++ public void dispense(World worldIn, BlockPos pos) + { + BlockSourceImpl blocksourceimpl = new BlockSourceImpl(worldIn, pos); + TileEntityDispenser tileentitydispenser = (TileEntityDispenser)blocksourceimpl.getBlockTileEntity(); +@@ -56,9 +59,22 @@ + } + else + { +- itemstack1 = TileEntityHopper.putStackInInventoryAllSlots(tileentitydispenser, iinventory, itemstack.copy().splitStack(1), enumfacing.getOpposite()); ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack.copy().splitStack(1)); ++ org.bukkit.inventory.Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof InventoryLargeChest) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); ++ } else { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } + +- if (itemstack1.isEmpty()) ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentitydispenser.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ itemstack1 = TileEntityHopper.putStackInInventoryAllSlots(tileentitydispenser, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumfacing.getOpposite()); ++ if (event.getItem().equals(oitemstack) && itemstack1.isEmpty()) + { + itemstack1 = itemstack.copy(); + itemstack1.shrink(1); diff --git a/patches/net/minecraft/block/BlockDynamicLiquid.java.patch b/patches/net/minecraft/block/BlockDynamicLiquid.java.patch new file mode 100644 index 00000000..0e966c15 --- /dev/null +++ b/patches/net/minecraft/block/BlockDynamicLiquid.java.patch @@ -0,0 +1,68 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockDynamicLiquid.java ++++ ../src-work/minecraft/net/minecraft/block/BlockDynamicLiquid.java +@@ -9,6 +9,8 @@ + import net.minecraft.util.EnumFacing; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.block.BlockFace; ++import org.bukkit.event.block.BlockFromToEvent; + + public class BlockDynamicLiquid extends BlockLiquid + { +@@ -82,7 +84,7 @@ + } + } + +- if (this.blockMaterial == Material.LAVA && i < 8 && i1 < 8 && i1 > i && rand.nextInt(4) != 0) ++ if (this.blockMaterial == Material.LAVA && i < 8 && i1 < 8 && i1 > i && rand.nextInt(4) != 0) // Paper + { + k *= 4; + } +@@ -113,14 +115,22 @@ + this.placeStaticBlock(worldIn, pos, state); + } + ++ org.bukkit.block.Block source = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + IBlockState iblockstate1 = worldIn.getBlockState(pos.down()); + + if (this.canFlowInto(worldIn, pos.down(), iblockstate1)) + { ++ BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } + if (this.blockMaterial == Material.LAVA && worldIn.getBlockState(pos.down()).getMaterial() == Material.WATER) + { +- worldIn.setBlockState(pos.down(), net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, pos.down(), pos, Blocks.STONE.getDefaultState())); +- this.triggerMixEffects(worldIn, pos.down()); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, pos.down(), net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, pos.down(), pos, Blocks.STONE.getDefaultState()), null)) { ++ this.triggerMixEffects(worldIn, pos.down()); ++ } + return; + } + +@@ -150,14 +160,20 @@ + + for (EnumFacing enumfacing1 : set) + { +- this.tryFlowInto(worldIn, pos.offset(enumfacing1), worldIn.getBlockState(pos.offset(enumfacing1)), k1); ++ // CraftBukkit start ++ BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumfacing1)); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.tryFlowInto(worldIn, pos.offset(enumfacing1), worldIn.getBlockState(pos.offset(enumfacing1)), k1); ++ } + } + } + } + + private void tryFlowInto(World worldIn, BlockPos pos, IBlockState state, int level) + { +- if (this.canFlowInto(worldIn, pos, state)) ++ if (worldIn.isBlockLoaded(pos) && this.canFlowInto(worldIn, pos, state)) // CraftBukkit - add isLoaded check + { + if (state.getMaterial() != Material.AIR) + { diff --git a/patches/net/minecraft/block/BlockEndPortal.java.patch b/patches/net/minecraft/block/BlockEndPortal.java.patch new file mode 100644 index 00000000..5415c351 --- /dev/null +++ b/patches/net/minecraft/block/BlockEndPortal.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockEndPortal.java ++++ ../src-work/minecraft/net/minecraft/block/BlockEndPortal.java +@@ -19,6 +19,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityPortalEnterEvent; + + public class BlockEndPortal extends BlockContainer + { +@@ -69,6 +70,8 @@ + { + if (!worldIn.isRemote && !entityIn.isRiding() && !entityIn.isBeingRidden() && entityIn.isNonBoss() && entityIn.getEntityBoundingBox().intersects(state.getBoundingBox(worldIn, pos).offset(pos))) + { ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entityIn.getBukkitEntity(), new org.bukkit.Location(worldIn.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent(event); + entityIn.changeDimension(1); + } + } diff --git a/patches/net/minecraft/block/BlockFarmland.java.patch b/patches/net/minecraft/block/BlockFarmland.java.patch new file mode 100644 index 00000000..4668c3c7 --- /dev/null +++ b/patches/net/minecraft/block/BlockFarmland.java.patch @@ -0,0 +1,67 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockFarmland.java ++++ ../src-work/minecraft/net/minecraft/block/BlockFarmland.java +@@ -19,6 +19,8 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityInteractEvent; + + public class BlockFarmland extends Block + { +@@ -72,16 +74,36 @@ + + public void onFallenUpon(World worldIn, BlockPos pos, Entity entityIn, float fallDistance) + { ++ super.onFallenUpon(worldIn, pos, entityIn, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. + if (net.minecraftforge.common.ForgeHooks.onFarmlandTrample(worldIn, pos, Blocks.DIRT.getDefaultState(), fallDistance, entityIn)) // Forge: Move logic to Entity#canTrample + { ++ org.bukkit.event.Cancellable cancellable; ++ if (entityIn instanceof EntityPlayer) { ++ cancellable = CraftEventFactory.callPlayerInteractEvent((EntityPlayer) entityIn, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entityIn.getBukkitEntity(), worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ if (cancellable.isCancelled()) { ++ return; ++ } ++ ++ if (CraftEventFactory.callEntityChangeBlockEvent(entityIn, pos, Blocks.DIRT, 0).isCancelled()) { ++ return; ++ } + turnToDirt(worldIn, pos); + } + +- super.onFallenUpon(worldIn, pos, entityIn, fallDistance); ++// super.onFallenUpon(worldIn, pos, entityIn, fallDistance); // CraftBukkit - moved up + } + + protected static void turnToDirt(World p_190970_0_, BlockPos worldIn) + { ++ org.bukkit.block.Block block = p_190970_0_.getWorld().getBlockAt(worldIn.getX(), worldIn.getY(), worldIn.getZ()); ++ if (CraftEventFactory.callBlockFadeEvent(block, Blocks.DIRT).isCancelled()) { ++ return; ++ } + p_190970_0_.setBlockState(worldIn, Blocks.DIRT.getDefaultState()); + AxisAlignedBB axisalignedbb = field_194405_c.offset(worldIn); + +@@ -95,7 +117,7 @@ + private boolean hasCrops(World worldIn, BlockPos pos) + { + Block block = worldIn.getBlockState(pos.up()).getBlock(); +- return block instanceof net.minecraftforge.common.IPlantable && canSustainPlant(worldIn.getBlockState(pos), worldIn, pos, net.minecraft.util.EnumFacing.UP, (net.minecraftforge.common.IPlantable)block); ++ return block instanceof net.minecraftforge.common.IPlantable && canSustainPlant(worldIn.getBlockState(pos), worldIn, pos, EnumFacing.UP, (net.minecraftforge.common.IPlantable)block); + } + + private boolean hasWater(World worldIn, BlockPos pos) +@@ -108,7 +130,7 @@ + } + } + +- return net.minecraftforge.common.FarmlandWaterManager.hasBlockWaterTicket(worldIn, pos); ++ return false; + } + + public void neighborChanged(IBlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos) diff --git a/patches/net/minecraft/block/BlockFire.java.patch b/patches/net/minecraft/block/BlockFire.java.patch new file mode 100644 index 00000000..96963068 --- /dev/null +++ b/patches/net/minecraft/block/BlockFire.java.patch @@ -0,0 +1,186 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockFire.java ++++ ../src-work/minecraft/net/minecraft/block/BlockFire.java +@@ -22,9 +22,11 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; +-import net.minecraft.world.WorldProviderEnd; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.block.BlockBurnEvent; ++import org.bukkit.event.block.BlockSpreadEvent; + + public class BlockFire extends Block + { +@@ -138,7 +140,7 @@ + if (!worldIn.isAreaLoaded(pos, 2)) return; // Forge: prevent loading unloaded chunks when spreading fire + if (!this.canPlaceBlockAt(worldIn, pos)) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); // CraftBukkit - invalid place location + } + + Block block = worldIn.getBlockState(pos.down()).getBlock(); +@@ -148,7 +150,7 @@ + + if (!flag && worldIn.isRaining() && this.canDie(worldIn, pos) && rand.nextFloat() < 0.2F + (float)i * 0.03F) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); // CraftBukkit - extinguished by rain + } + else + { +@@ -166,7 +168,7 @@ + { + if (!worldIn.getBlockState(pos.down()).isSideSolid(worldIn, pos.down(), EnumFacing.UP) || i > 3) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); + } + + return; +@@ -174,7 +176,7 @@ + + if (!this.canCatchFire(worldIn, pos.down(), EnumFacing.UP) && i == 15 && rand.nextInt(4) == 0) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); + return; + } + } +@@ -187,12 +189,14 @@ + j = -50; + } + +- this.tryCatchFire(worldIn, pos.east(), 300 + j, rand, i, EnumFacing.WEST); +- this.tryCatchFire(worldIn, pos.west(), 300 + j, rand, i, EnumFacing.EAST); +- this.tryCatchFire(worldIn, pos.down(), 250 + j, rand, i, EnumFacing.UP); +- this.tryCatchFire(worldIn, pos.up(), 250 + j, rand, i, EnumFacing.DOWN); +- this.tryCatchFire(worldIn, pos.north(), 300 + j, rand, i, EnumFacing.SOUTH); +- this.tryCatchFire(worldIn, pos.south(), 300 + j, rand, i, EnumFacing.NORTH); ++ // CraftBukkit start - add source blockposition to burn calls ++ this.tryCatchFire(worldIn, pos.east(), 300 + j, rand, i, EnumFacing.WEST, pos); ++ this.tryCatchFire(worldIn, pos.west(), 300 + j, rand, i, EnumFacing.EAST, pos); ++ this.tryCatchFire(worldIn, pos.down(), 250 + j, rand, i, EnumFacing.UP, pos); ++ this.tryCatchFire(worldIn, pos.up(), 250 + j, rand, i, EnumFacing.DOWN, pos); ++ this.tryCatchFire(worldIn, pos.north(), 300 + j, rand, i, EnumFacing.SOUTH, pos); ++ this.tryCatchFire(worldIn, pos.south(), 300 + j, rand, i, EnumFacing.NORTH, pos); ++ // CraftBukkit end + + for (int k = -1; k <= 1; ++k) + { +@@ -210,6 +214,7 @@ + } + + BlockPos blockpos = pos.add(k, i1, l); ++ if (!worldIn.isBlockLoaded(blockpos)) continue; // Paper + int k1 = this.getNeighborEncouragement(worldIn, blockpos); + + if (k1 > 0) +@@ -230,7 +235,26 @@ + i2 = 15; + } + +- worldIn.setBlockState(blockpos, state.withProperty(AGE, Integer.valueOf(i2)), 3); ++ // CraftBukkit start - Call to stop spread of fire ++ if (worldIn.getBlockState(blockpos) != Blocks.FIRE) { ++ if (CraftEventFactory.callBlockIgniteEvent(worldIn, blockpos.getX(), blockpos.getY(), blockpos.getZ(), pos.getX(), pos.getY(), pos.getZ()).isCancelled()) { ++ continue; ++ } ++ ++ org.bukkit.Server server = worldIn.getServer(); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.block.BlockState blockState = bworld.getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()).getState(); ++ blockState.setTypeId(Block.getIdFromBlock(this)); ++ blockState.setData(new org.bukkit.material.MaterialData(Block.getIdFromBlock(this), (byte) l1)); ++ ++ BlockSpreadEvent spreadEvent = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), blockState); ++ server.getPluginManager().callEvent(spreadEvent); ++ ++ if (!spreadEvent.isCancelled()) { ++ blockState.update(true); ++ } ++ } ++ // CraftBukkit end + } + } + } +@@ -302,6 +326,48 @@ + } + } + ++ // Atom: Create a new method, instead of the method modification above for sanity ++ private void tryCatchFire(World worldIn, BlockPos pos, int chance, Random random, int age, EnumFacing face, BlockPos sourcePos) ++ { ++ final IBlockState iblockstate = worldIn.getBlockState(pos); ++ if (iblockstate == null) return; ++ int i = worldIn.getBlockState(pos).getBlock().getFlammability(worldIn, pos, face); ++ ++ if (random.nextInt(chance) < i) ++ { ++ org.bukkit.block.Block theBlock = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block sourceBlock = worldIn.getWorld().getBlockAt(sourcePos.getX(), sourcePos.getY(), sourcePos.getZ()); ++ ++ BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ if (random.nextInt(age + 10) < 5 && !worldIn.isRainingAt(pos)) ++ { ++ int j = age + random.nextInt(5) / 4; ++ ++ if (j > 15) ++ { ++ j = 15; ++ } ++ ++ worldIn.setBlockState(pos, this.getDefaultState().withProperty(AGE, Integer.valueOf(j)), 3); ++ } ++ else ++ { ++ worldIn.setBlockToAir(pos); ++ } ++ ++ if (iblockstate.getBlock() == Blocks.TNT) ++ { ++ Blocks.TNT.onBlockDestroyedByPlayer(worldIn, pos, iblockstate.withProperty(BlockTNT.EXPLODE, Boolean.valueOf(true))); ++ } ++ } ++ } ++ + private boolean canNeighborCatchFire(World worldIn, BlockPos pos) + { + for (EnumFacing enumfacing : EnumFacing.values()) +@@ -354,7 +420,7 @@ + { + if (!worldIn.getBlockState(pos.down()).isTopSolid() && !this.canNeighborCatchFire(worldIn, pos)) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); // CraftBukkit - fuel block gone + } + } + +@@ -364,7 +430,7 @@ + { + if (!worldIn.getBlockState(pos.down()).isTopSolid() && !this.canNeighborCatchFire(worldIn, pos)) + { +- worldIn.setBlockToAir(pos); ++ fireExtinguished(worldIn, pos); // CraftBukkit - fuel block broke + } + else + { +@@ -495,4 +561,10 @@ + { + return BlockFaceShape.UNDEFINED; + } ++ ++ private void fireExtinguished(World world, BlockPos position) { ++ if (!CraftEventFactory.callBlockFadeEvent(world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), Blocks.AIR).isCancelled()) { ++ world.setBlockToAir(position); ++ } ++ } + } diff --git a/patches/net/minecraft/block/BlockGrass.java.patch b/patches/net/minecraft/block/BlockGrass.java.patch new file mode 100644 index 00000000..8001a5b1 --- /dev/null +++ b/patches/net/minecraft/block/BlockGrass.java.patch @@ -0,0 +1,90 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockGrass.java ++++ ../src-work/minecraft/net/minecraft/block/BlockGrass.java +@@ -15,6 +15,11 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.event.block.BlockFadeEvent; ++import org.bukkit.event.block.BlockSpreadEvent; + + public class BlockGrass extends Block implements IGrowable + { +@@ -38,30 +43,58 @@ + { + if (!worldIn.isRemote) + { ++ int lightLevel = -1; // Paper + if (!worldIn.isAreaLoaded(pos, 3)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light and spreading +- if (worldIn.getLightFromNeighbors(pos.up()) < 4 && worldIn.getBlockState(pos.up()).getLightOpacity(worldIn, pos.up()) > 2) ++ if (worldIn.getBlockState(pos.up()).getLightOpacity(worldIn, pos.up()) > 2 && (lightLevel = worldIn.getLightFromNeighbors(pos.up())) < 4 ) + { +- worldIn.setBlockState(pos, Blocks.DIRT.getDefaultState()); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ BlockState blockState = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); ++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.DIRT)); ++ ++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ blockState.update(true); ++ } + } + else + { +- if (worldIn.getLightFromNeighbors(pos.up()) >= 9) ++ // Paper start ++ // If light was calculated above, reuse it, else grab it ++ if (lightLevel == -1) { ++ lightLevel = worldIn.getLightFromNeighbors(pos.up()); ++ } ++ if (lightLevel >= 9) + { ++ // Paper end + for (int i = 0; i < 4; ++i) + { + BlockPos blockpos = pos.add(rand.nextInt(3) - 1, rand.nextInt(5) - 3, rand.nextInt(3) - 1); + +- if (blockpos.getY() >= 0 && blockpos.getY() < 256 && !worldIn.isBlockLoaded(blockpos)) ++ IBlockState iblockstate2 = worldIn.getBlockState(blockpos); // Paper - moved up ++ if (iblockstate2 == null) + { + return; + } + + IBlockState iblockstate = worldIn.getBlockState(blockpos.up()); +- IBlockState iblockstate1 = worldIn.getBlockState(blockpos); ++ //IBlockState iblockstate1 = worldIn.getBlockState(blockpos); // Paper - moved up + +- if (iblockstate1.getBlock() == Blocks.DIRT && iblockstate1.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT && worldIn.getLightFromNeighbors(blockpos.up()) >= 4 && iblockstate.getLightOpacity(worldIn, pos.up()) <= 2) ++ // Paper - move last check before isLightLevel to avoid unneeded light checks ++ if (iblockstate2.getBlock() == Blocks.DIRT && iblockstate2.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT && iblockstate.getLightOpacity(worldIn, pos.up()) <= 2 && worldIn.isLightLevel(blockpos.up(), 4)) + { +- worldIn.setBlockState(blockpos, Blocks.GRASS.getDefaultState()); ++// worldIn.setBlockState(blockpos, Blocks.GRASS.getDefaultState()); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ BlockState blockState = bworld.getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()).getState(); ++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.GRASS)); ++ ++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), blockState); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ blockState.update(true); ++ } + } + } + } +@@ -109,7 +142,8 @@ + + if (Blocks.TALLGRASS.canBlockStay(worldIn, blockpos1, iblockstate1)) + { +- worldIn.setBlockState(blockpos1, iblockstate1, 3); ++// worldIn.setBlockState(blockpos1, iblockstate1, 3); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, blockpos1.getX(), blockpos1.getY(), blockpos1.getZ(), iblockstate1.getBlock(), iblockstate1.getBlock().getMetaFromState(iblockstate1)); + } + } + } diff --git a/patches/net/minecraft/block/BlockIce.java.patch b/patches/net/minecraft/block/BlockIce.java.patch new file mode 100644 index 00000000..83339cdb --- /dev/null +++ b/patches/net/minecraft/block/BlockIce.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockIce.java ++++ ../src-work/minecraft/net/minecraft/block/BlockIce.java +@@ -86,6 +86,9 @@ + + protected void turnIntoWater(World worldIn, BlockPos pos) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), worldIn.provider.doesWaterVaporize() ? Blocks.AIR : Blocks.WATER).isCancelled()) { ++ return; ++ } + if (worldIn.provider.doesWaterVaporize()) + { + worldIn.setBlockToAir(pos); diff --git a/patches/net/minecraft/block/BlockJukebox.java.patch b/patches/net/minecraft/block/BlockJukebox.java.patch new file mode 100644 index 00000000..22d8f7de --- /dev/null +++ b/patches/net/minecraft/block/BlockJukebox.java.patch @@ -0,0 +1,23 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockJukebox.java ++++ ../src-work/minecraft/net/minecraft/block/BlockJukebox.java +@@ -66,7 +66,7 @@ + } + } + +- private void dropRecord(World worldIn, BlockPos pos, IBlockState state) ++ public void dropRecord(World worldIn, BlockPos pos, IBlockState state) + { + if (!worldIn.isRemote) + { +@@ -193,6 +193,11 @@ + + public void setRecord(ItemStack recordStack) + { ++ // CraftBukkit start - There can only be one ++ if (!recordStack.isEmpty()) { ++ recordStack.setCount(1); ++ } ++ // CraftBukkit end + this.record = recordStack; + this.markDirty(); + } diff --git a/patches/net/minecraft/block/BlockLeaves.java.patch b/patches/net/minecraft/block/BlockLeaves.java.patch new file mode 100644 index 00000000..1dfcacf0 --- /dev/null +++ b/patches/net/minecraft/block/BlockLeaves.java.patch @@ -0,0 +1,23 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockLeaves.java ++++ ../src-work/minecraft/net/minecraft/block/BlockLeaves.java +@@ -16,6 +16,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.LeavesDecayEvent; + + public abstract class BlockLeaves extends Block implements net.minecraftforge.common.IShearable + { +@@ -178,6 +179,12 @@ + + private void destroy(World worldIn, BlockPos pos) + { ++ LeavesDecayEvent event = new LeavesDecayEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || worldIn.getBlockState(pos).getBlock() != this) { ++ return; ++ } + this.dropBlockAsItem(worldIn, pos, worldIn.getBlockState(pos), 0); + worldIn.setBlockToAir(pos); + } diff --git a/patches/net/minecraft/block/BlockLever.java.patch b/patches/net/minecraft/block/BlockLever.java.patch new file mode 100644 index 00000000..be12b8a4 --- /dev/null +++ b/patches/net/minecraft/block/BlockLever.java.patch @@ -0,0 +1,257 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockLever.java ++++ ../src-work/minecraft/net/minecraft/block/BlockLever.java +@@ -22,10 +22,11 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockLever extends Block + { +- public static final PropertyEnum FACING = PropertyEnum.create("facing", BlockLever.EnumOrientation.class); ++ public static final PropertyEnum FACING = PropertyEnum.create("facing", EnumOrientation.class); + public static final PropertyBool POWERED = PropertyBool.create("powered"); + protected static final AxisAlignedBB LEVER_NORTH_AABB = new AxisAlignedBB(0.3125D, 0.20000000298023224D, 0.625D, 0.6875D, 0.800000011920929D, 1.0D); + protected static final AxisAlignedBB LEVER_SOUTH_AABB = new AxisAlignedBB(0.3125D, 0.20000000298023224D, 0.0D, 0.6875D, 0.800000011920929D, 0.375D); +@@ -37,7 +38,7 @@ + protected BlockLever() + { + super(Material.CIRCUITS); +- this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, BlockLever.EnumOrientation.NORTH).withProperty(POWERED, Boolean.valueOf(false))); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumOrientation.NORTH).withProperty(POWERED, Boolean.valueOf(false))); + this.setCreativeTab(CreativeTabs.REDSTONE); + } + +@@ -86,7 +87,7 @@ + + if (canAttachTo(worldIn, pos, facing)) + { +- return iblockstate.withProperty(FACING, BlockLever.EnumOrientation.forFacings(facing, placer.getHorizontalFacing())); ++ return iblockstate.withProperty(FACING, EnumOrientation.forFacings(facing, placer.getHorizontalFacing())); + } + else + { +@@ -94,13 +95,13 @@ + { + if (enumfacing != facing && canAttachTo(worldIn, pos, enumfacing)) + { +- return iblockstate.withProperty(FACING, BlockLever.EnumOrientation.forFacings(enumfacing, placer.getHorizontalFacing())); ++ return iblockstate.withProperty(FACING, EnumOrientation.forFacings(enumfacing, placer.getHorizontalFacing())); + } + } + + if (worldIn.getBlockState(pos.down()).isTopSolid()) + { +- return iblockstate.withProperty(FACING, BlockLever.EnumOrientation.forFacings(EnumFacing.UP, placer.getHorizontalFacing())); ++ return iblockstate.withProperty(FACING, EnumOrientation.forFacings(EnumFacing.UP, placer.getHorizontalFacing())); + } + else + { +@@ -111,7 +112,7 @@ + + public void neighborChanged(IBlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos) + { +- if (this.checkCanSurvive(worldIn, pos, state) && !canAttachTo(worldIn, pos, ((BlockLever.EnumOrientation)state.getValue(FACING)).getFacing())) ++ if (this.checkCanSurvive(worldIn, pos, state) && !canAttachTo(worldIn, pos, ((EnumOrientation)state.getValue(FACING)).getFacing())) + { + this.dropBlockAsItem(worldIn, pos, state, 0); + worldIn.setBlockToAir(pos); +@@ -134,7 +135,7 @@ + + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) + { +- switch ((BlockLever.EnumOrientation)state.getValue(FACING)) ++ switch ((EnumOrientation)state.getValue(FACING)) + { + case EAST: + default: +@@ -162,12 +163,23 @@ + } + else + { ++ boolean powered = state.getValue(POWERED); ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int old = (powered) ? 15 : 0; ++ int current = (!powered) ? 15 : 0; ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if ((eventRedstone.getNewCurrent() > 0) == powered) { ++ return true; ++ } + state = state.cycleProperty(POWERED); + worldIn.setBlockState(pos, state, 3); + float f = ((Boolean)state.getValue(POWERED)).booleanValue() ? 0.6F : 0.5F; + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.BLOCK_LEVER_CLICK, SoundCategory.BLOCKS, 0.3F, f); + worldIn.notifyNeighborsOfStateChange(pos, this, false); +- EnumFacing enumfacing = ((BlockLever.EnumOrientation)state.getValue(FACING)).getFacing(); ++ EnumFacing enumfacing = ((EnumOrientation)state.getValue(FACING)).getFacing(); + worldIn.notifyNeighborsOfStateChange(pos.offset(enumfacing.getOpposite()), this, false); + return true; + } +@@ -178,7 +190,7 @@ + if (((Boolean)state.getValue(POWERED)).booleanValue()) + { + worldIn.notifyNeighborsOfStateChange(pos, this, false); +- EnumFacing enumfacing = ((BlockLever.EnumOrientation)state.getValue(FACING)).getFacing(); ++ EnumFacing enumfacing = ((EnumOrientation)state.getValue(FACING)).getFacing(); + worldIn.notifyNeighborsOfStateChange(pos.offset(enumfacing.getOpposite()), this, false); + } + +@@ -198,7 +210,7 @@ + } + else + { +- return ((BlockLever.EnumOrientation)blockState.getValue(FACING)).getFacing() == side ? 15 : 0; ++ return ((EnumOrientation)blockState.getValue(FACING)).getFacing() == side ? 15 : 0; + } + } + +@@ -209,13 +221,13 @@ + + public IBlockState getStateFromMeta(int meta) + { +- return this.getDefaultState().withProperty(FACING, BlockLever.EnumOrientation.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); ++ return this.getDefaultState().withProperty(FACING, EnumOrientation.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); + } + + public int getMetaFromState(IBlockState state) + { + int i = 0; +- i = i | ((BlockLever.EnumOrientation)state.getValue(FACING)).getMetadata(); ++ i = i | ((EnumOrientation)state.getValue(FACING)).getMetadata(); + + if (((Boolean)state.getValue(POWERED)).booleanValue()) + { +@@ -231,62 +243,62 @@ + { + case CLOCKWISE_180: + +- switch ((BlockLever.EnumOrientation)state.getValue(FACING)) ++ switch ((EnumOrientation)state.getValue(FACING)) + { + case EAST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.WEST); ++ return state.withProperty(FACING, EnumOrientation.WEST); + case WEST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.EAST); ++ return state.withProperty(FACING, EnumOrientation.EAST); + case SOUTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.NORTH); ++ return state.withProperty(FACING, EnumOrientation.NORTH); + case NORTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.SOUTH); ++ return state.withProperty(FACING, EnumOrientation.SOUTH); + default: + return state; + } + + case COUNTERCLOCKWISE_90: + +- switch ((BlockLever.EnumOrientation)state.getValue(FACING)) ++ switch ((EnumOrientation)state.getValue(FACING)) + { + case EAST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.NORTH); ++ return state.withProperty(FACING, EnumOrientation.NORTH); + case WEST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.SOUTH); ++ return state.withProperty(FACING, EnumOrientation.SOUTH); + case SOUTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.EAST); ++ return state.withProperty(FACING, EnumOrientation.EAST); + case NORTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.WEST); ++ return state.withProperty(FACING, EnumOrientation.WEST); + case UP_Z: +- return state.withProperty(FACING, BlockLever.EnumOrientation.UP_X); ++ return state.withProperty(FACING, EnumOrientation.UP_X); + case UP_X: +- return state.withProperty(FACING, BlockLever.EnumOrientation.UP_Z); ++ return state.withProperty(FACING, EnumOrientation.UP_Z); + case DOWN_X: +- return state.withProperty(FACING, BlockLever.EnumOrientation.DOWN_Z); ++ return state.withProperty(FACING, EnumOrientation.DOWN_Z); + case DOWN_Z: +- return state.withProperty(FACING, BlockLever.EnumOrientation.DOWN_X); ++ return state.withProperty(FACING, EnumOrientation.DOWN_X); + } + + case CLOCKWISE_90: + +- switch ((BlockLever.EnumOrientation)state.getValue(FACING)) ++ switch ((EnumOrientation)state.getValue(FACING)) + { + case EAST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.SOUTH); ++ return state.withProperty(FACING, EnumOrientation.SOUTH); + case WEST: +- return state.withProperty(FACING, BlockLever.EnumOrientation.NORTH); ++ return state.withProperty(FACING, EnumOrientation.NORTH); + case SOUTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.WEST); ++ return state.withProperty(FACING, EnumOrientation.WEST); + case NORTH: +- return state.withProperty(FACING, BlockLever.EnumOrientation.EAST); ++ return state.withProperty(FACING, EnumOrientation.EAST); + case UP_Z: +- return state.withProperty(FACING, BlockLever.EnumOrientation.UP_X); ++ return state.withProperty(FACING, EnumOrientation.UP_X); + case UP_X: +- return state.withProperty(FACING, BlockLever.EnumOrientation.UP_Z); ++ return state.withProperty(FACING, EnumOrientation.UP_Z); + case DOWN_X: +- return state.withProperty(FACING, BlockLever.EnumOrientation.DOWN_Z); ++ return state.withProperty(FACING, EnumOrientation.DOWN_Z); + case DOWN_Z: +- return state.withProperty(FACING, BlockLever.EnumOrientation.DOWN_X); ++ return state.withProperty(FACING, EnumOrientation.DOWN_X); + } + + default: +@@ -296,7 +308,7 @@ + + public IBlockState withMirror(IBlockState state, Mirror mirrorIn) + { +- return state.withRotation(mirrorIn.toRotation(((BlockLever.EnumOrientation)state.getValue(FACING)).getFacing())); ++ return state.withRotation(mirrorIn.toRotation(((EnumOrientation)state.getValue(FACING)).getFacing())); + } + + protected BlockStateContainer createBlockState() +@@ -320,7 +332,7 @@ + UP_X(6, "up_x", EnumFacing.UP), + DOWN_Z(7, "down_z", EnumFacing.DOWN); + +- private static final BlockLever.EnumOrientation[] META_LOOKUP = new BlockLever.EnumOrientation[values().length]; ++ private static final EnumOrientation[] META_LOOKUP = new EnumOrientation[values().length]; + private final int meta; + private final String name; + private final EnumFacing facing; +@@ -347,7 +359,7 @@ + return this.name; + } + +- public static BlockLever.EnumOrientation byMetadata(int meta) ++ public static EnumOrientation byMetadata(int meta) + { + if (meta < 0 || meta >= META_LOOKUP.length) + { +@@ -357,7 +369,7 @@ + return META_LOOKUP[meta]; + } + +- public static BlockLever.EnumOrientation forFacings(EnumFacing clickedSide, EnumFacing entityFacing) ++ public static EnumOrientation forFacings(EnumFacing clickedSide, EnumFacing entityFacing) + { + switch (clickedSide) + { +@@ -405,7 +417,7 @@ + + static + { +- for (BlockLever.EnumOrientation blocklever$enumorientation : values()) ++ for (EnumOrientation blocklever$enumorientation : values()) + { + META_LOOKUP[blocklever$enumorientation.getMetadata()] = blocklever$enumorientation; + } diff --git a/patches/net/minecraft/block/BlockLilyPad.java.patch b/patches/net/minecraft/block/BlockLilyPad.java.patch new file mode 100644 index 00000000..a69a237c --- /dev/null +++ b/patches/net/minecraft/block/BlockLilyPad.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockLilyPad.java ++++ ../src-work/minecraft/net/minecraft/block/BlockLilyPad.java +@@ -34,7 +34,7 @@ + { + super.onEntityCollidedWithBlock(worldIn, pos, state, entityIn); + +- if (entityIn instanceof EntityBoat) ++ if (entityIn instanceof EntityBoat && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityIn, pos, Blocks.AIR, 0).isCancelled()) + { + worldIn.destroyBlock(new BlockPos(pos), true); + } diff --git a/patches/net/minecraft/block/BlockLiquid.java.patch b/patches/net/minecraft/block/BlockLiquid.java.patch new file mode 100644 index 00000000..51b1bfbc --- /dev/null +++ b/patches/net/minecraft/block/BlockLiquid.java.patch @@ -0,0 +1,23 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockLiquid.java ++++ ../src-work/minecraft/net/minecraft/block/BlockLiquid.java +@@ -285,14 +285,18 @@ + if (integer.intValue() == 0) + { + worldIn.setBlockState(pos, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, pos, pos, Blocks.OBSIDIAN.getDefaultState())); +- this.triggerMixEffects(worldIn, pos); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, pos, Blocks.OBSIDIAN.getDefaultState(), null)) { ++ this.triggerMixEffects(worldIn, pos); ++ } + return true; + } + + if (integer.intValue() <= 4) + { + worldIn.setBlockState(pos, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, pos, pos, Blocks.COBBLESTONE.getDefaultState())); +- this.triggerMixEffects(worldIn, pos); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, pos, Blocks.COBBLESTONE.getDefaultState(), null)) { ++ this.triggerMixEffects(worldIn, pos); ++ } + return true; + } + } diff --git a/patches/net/minecraft/block/BlockMagma.java.patch b/patches/net/minecraft/block/BlockMagma.java.patch new file mode 100644 index 00000000..19cffe8d --- /dev/null +++ b/patches/net/minecraft/block/BlockMagma.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockMagma.java ++++ ../src-work/minecraft/net/minecraft/block/BlockMagma.java +@@ -40,7 +40,9 @@ + { + if (!entityIn.isImmuneToFire() && entityIn instanceof EntityLivingBase && !EnchantmentHelper.hasFrostWalkerEnchantment((EntityLivingBase)entityIn)) + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + entityIn.attackEntityFrom(DamageSource.HOT_FLOOR, 1.0F); ++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; + } + + super.onEntityWalk(worldIn, pos, entityIn); diff --git a/patches/net/minecraft/block/BlockMushroom.java.patch b/patches/net/minecraft/block/BlockMushroom.java.patch new file mode 100644 index 00000000..b4e8c32d --- /dev/null +++ b/patches/net/minecraft/block/BlockMushroom.java.patch @@ -0,0 +1,54 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockMushroom.java ++++ ../src-work/minecraft/net/minecraft/block/BlockMushroom.java +@@ -9,6 +9,9 @@ + import net.minecraft.world.World; + import net.minecraft.world.gen.feature.WorldGenBigMushroom; + import net.minecraft.world.gen.feature.WorldGenerator; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.event.block.BlockSpreadEvent; + + public class BlockMushroom extends BlockBush implements IGrowable + { +@@ -26,7 +29,8 @@ + + public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand) + { +- if (rand.nextInt(25) == 0) ++ final int sourceX = pos.getX(), sourceY = pos.getY(), sourceZ = pos.getZ(); ++ if (rand.nextInt(Math.max(1, (int)(100.0f / worldIn.spigotConfig.mushroomModifier) * 25)) == 0) //Spigot + { + int i = 5; + int j = 4; +@@ -58,7 +62,17 @@ + + if (worldIn.isAirBlock(blockpos1) && this.canBlockStay(worldIn, blockpos1, this.getDefaultState())) + { +- worldIn.setBlockState(blockpos1, this.getDefaultState(), 2); ++// worldIn.setBlockState(blockpos1, this.getDefaultState(), 2); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ BlockState blockState = bworld.getBlockAt(blockpos1.getX(), blockpos1.getY(), blockpos1.getZ()).getState(); ++ blockState.setType(org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this)); // nms: this.id, 0, 2 ++ ++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(sourceX, sourceY, sourceZ), blockState); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ blockState.update(true); ++ } + } + } + } +@@ -105,10 +119,12 @@ + + if (this == Blocks.BROWN_MUSHROOM) + { ++ BlockSapling.treeType = TreeType.BROWN_MUSHROOM; + worldgenerator = new WorldGenBigMushroom(Blocks.BROWN_MUSHROOM_BLOCK); + } + else if (this == Blocks.RED_MUSHROOM) + { ++ BlockSapling.treeType = TreeType.RED_MUSHROOM; + worldgenerator = new WorldGenBigMushroom(Blocks.RED_MUSHROOM_BLOCK); + } + diff --git a/patches/net/minecraft/block/BlockMycelium.java.patch b/patches/net/minecraft/block/BlockMycelium.java.patch new file mode 100644 index 00000000..a4dbc573 --- /dev/null +++ b/patches/net/minecraft/block/BlockMycelium.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockMycelium.java ++++ ../src-work/minecraft/net/minecraft/block/BlockMycelium.java +@@ -16,6 +16,10 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.event.block.BlockFadeEvent; ++import org.bukkit.event.block.BlockSpreadEvent; + + public class BlockMycelium extends Block + { +@@ -42,7 +46,17 @@ + if (!worldIn.isAreaLoaded(pos, 2)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light and spreading + if (worldIn.getLightFromNeighbors(pos.up()) < 4 && worldIn.getBlockState(pos.up()).getLightOpacity(worldIn, pos.up()) > 2) + { +- worldIn.setBlockState(pos, Blocks.DIRT.getDefaultState().withProperty(BlockDirt.VARIANT, BlockDirt.DirtType.DIRT)); ++// worldIn.setBlockState(pos, Blocks.DIRT.getDefaultState().withProperty(BlockDirt.VARIANT, BlockDirt.DirtType.DIRT)); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ BlockState blockState = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); ++ blockState.setType(CraftMagicNumbers.getMaterial(Blocks.DIRT)); ++ ++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ blockState.update(true); ++ } + } + else + { +@@ -56,7 +70,17 @@ + + if (iblockstate.getBlock() == Blocks.DIRT && iblockstate.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT && worldIn.getLightFromNeighbors(blockpos.up()) >= 4 && iblockstate1.getLightOpacity(worldIn, blockpos.up()) <= 2) + { +- worldIn.setBlockState(blockpos, this.getDefaultState()); ++// worldIn.setBlockState(blockpos, this.getDefaultState()); ++ org.bukkit.World bworld = worldIn.getWorld(); ++ BlockState blockState = bworld.getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()).getState(); ++ blockState.setType(CraftMagicNumbers.getMaterial(this)); ++ ++ BlockSpreadEvent event = new BlockSpreadEvent(blockState.getBlock(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), blockState); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ blockState.update(true); ++ } + } + } + } diff --git a/patches/net/minecraft/block/BlockNetherWart.java.patch b/patches/net/minecraft/block/BlockNetherWart.java.patch new file mode 100644 index 00000000..2b41af38 --- /dev/null +++ b/patches/net/minecraft/block/BlockNetherWart.java.patch @@ -0,0 +1,16 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockNetherWart.java ++++ ../src-work/minecraft/net/minecraft/block/BlockNetherWart.java +@@ -49,10 +49,11 @@ + { + int i = ((Integer)state.getValue(AGE)).intValue(); + +- if (i < 3 && net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, rand.nextInt(10) == 0)) ++ if (i < 3 && net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, rand.nextInt(Math.max(1, (int)(100.0f / worldIn.spigotConfig.wartModifier) * 10)) == 0)) // Spigot + { + IBlockState newState = state.withProperty(AGE, Integer.valueOf(i + 1)); +- worldIn.setBlockState(pos, newState, 2); ++// worldIn.setBlockState(pos, newState, 2); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(newState)); + net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, newState); + } + diff --git a/patches/net/minecraft/block/BlockObserver.java.patch b/patches/net/minecraft/block/BlockObserver.java.patch new file mode 100644 index 00000000..0c196a4b --- /dev/null +++ b/patches/net/minecraft/block/BlockObserver.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockObserver.java ++++ ../src-work/minecraft/net/minecraft/block/BlockObserver.java +@@ -14,6 +14,7 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockObserver extends BlockDirectional + { +@@ -45,10 +46,20 @@ + { + if (((Boolean)state.getValue(POWERED)).booleanValue()) + { ++ // Paper start ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 15, 0).getNewCurrent() != 0) { ++ return; ++ } ++ // Paper end + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(false)), 2); + } + else + { ++ // Paper start ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 0, 15).getNewCurrent() != 15) { ++ return; ++ } ++ // Paper end + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(true)), 2); + worldIn.scheduleUpdate(pos, this, 2); + } diff --git a/patches/net/minecraft/block/BlockPistonBase.java.patch b/patches/net/minecraft/block/BlockPistonBase.java.patch new file mode 100644 index 00000000..9e7b618c --- /dev/null +++ b/patches/net/minecraft/block/BlockPistonBase.java.patch @@ -0,0 +1,96 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockPistonBase.java ++++ ../src-work/minecraft/net/minecraft/block/BlockPistonBase.java +@@ -1,6 +1,9 @@ + package net.minecraft.block; + ++import com.google.common.collect.ImmutableList; + import com.google.common.collect.Lists; ++ ++import java.util.AbstractList; + import java.util.List; + import javax.annotation.Nullable; + import net.minecraft.block.material.EnumPushReaction; +@@ -28,6 +31,9 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockPistonExtendEvent; ++import org.bukkit.event.block.BlockPistonRetractEvent; + + public class BlockPistonBase extends BlockDirectional + { +@@ -142,6 +148,15 @@ + } + else if (!flag && ((Boolean)state.getValue(EXTENDED)).booleanValue()) + { ++ if (!this.isSticky) { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumfacing)); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } + worldIn.addBlockEvent(pos, this, 1, enumfacing.getIndex()); + } + } +@@ -241,7 +256,8 @@ + } + } + +- if (!flag1 && !iblockstate.getBlock().isAir(iblockstate, worldIn, blockpos) && canPush(iblockstate, worldIn, blockpos, enumfacing.getOpposite(), false, enumfacing) && (iblockstate.getMobilityFlag() == EnumPushReaction.NORMAL || block == Blocks.PISTON || block == Blocks.STICKY_PISTON)) ++ // CraftBukkit - remove '!iblockstate.getBlock().isAir(iblockstate, worldIn, blockpos)' condition ++ if (!flag1 && canPush(iblockstate, worldIn, blockpos, enumfacing.getOpposite(), false, enumfacing) && (iblockstate.getMobilityFlag() == EnumPushReaction.NORMAL || block == Blocks.PISTON || block == Blocks.STICKY_PISTON)) + { + this.doMove(worldIn, pos, enumfacing, false); + } +@@ -349,6 +365,47 @@ + IBlockState[] aiblockstate = new IBlockState[k]; + EnumFacing enumfacing = extending ? direction : direction.getOpposite(); + ++ final org.bukkit.block.Block bblock = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ final List moved = blockpistonstructurehelper.getBlocksToMove(); ++ final List broken = blockpistonstructurehelper.getBlocksToDestroy(); ++ ++ List blocks = new AbstractList() { ++ ++ @Override ++ public int size() { ++ return moved.size() + broken.size(); ++ } ++ ++ @Override ++ public org.bukkit.block.Block get(int index) { ++ if (index >= size() || index < 0) { ++ throw new ArrayIndexOutOfBoundsException(index); ++ } ++ BlockPos pos = index < moved.size() ? moved.get(index) : broken.get(index - moved.size()); ++ return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ }; ++ org.bukkit.event.block.BlockPistonEvent event; ++ if (extending) { ++ event = new BlockPistonExtendEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumfacing)); ++ } else { ++ event = new BlockPistonRetractEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumfacing)); ++ } ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ for (BlockPos b : broken) { ++ worldIn.notifyBlockUpdate(b, Blocks.AIR.getDefaultState(), worldIn.getBlockState(b), 3); ++ } ++ for (BlockPos b : moved) { ++ worldIn.notifyBlockUpdate(b, Blocks.AIR.getDefaultState(), worldIn.getBlockState(b), 3); ++ b = b.offset(enumfacing); ++ worldIn.notifyBlockUpdate(b, Blocks.AIR.getDefaultState(), worldIn.getBlockState(b), 3); ++ } ++ return false; ++ } ++ + for (int j = list2.size() - 1; j >= 0; --j) + { + BlockPos blockpos1 = list2.get(j); diff --git a/patches/net/minecraft/block/BlockPortal.java.patch b/patches/net/minecraft/block/BlockPortal.java.patch new file mode 100644 index 00000000..ee1b0a75 --- /dev/null +++ b/patches/net/minecraft/block/BlockPortal.java.patch @@ -0,0 +1,188 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockPortal.java ++++ ../src-work/minecraft/net/minecraft/block/BlockPortal.java +@@ -29,6 +29,8 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityPortalEnterEvent; ++import org.bukkit.event.world.PortalCreateEvent; + + public class BlockPortal extends BlockBreakable + { +@@ -62,7 +64,7 @@ + { + super.updateTick(worldIn, pos, state, rand); + +- if (worldIn.provider.isSurfaceWorld() && worldIn.getGameRules().getBoolean("doMobSpawning") && rand.nextInt(2000) < worldIn.getDifficulty().getDifficultyId()) ++ if (worldIn.spigotConfig.enableZombiePigmenPortalSpawns && worldIn.provider.isSurfaceWorld() && worldIn.getGameRules().getBoolean("doMobSpawning") && rand.nextInt(2000) < worldIn.getDifficulty().getDifficultyId()) // Spigot + { + int i = pos.getY(); + BlockPos blockpos; +@@ -74,7 +76,7 @@ + + if (i > 0 && !worldIn.getBlockState(blockpos.up()).isNormalCube()) + { +- Entity entity = ItemMonsterPlacer.spawnCreature(worldIn, EntityList.getKey(EntityPigZombie.class), (double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 1.1D, (double)blockpos.getZ() + 0.5D); ++ Entity entity = ItemMonsterPlacer.spawnCreature(worldIn, EntityList.getKey(EntityPigZombie.class), (double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 1.1D, (double)blockpos.getZ() + 0.5D, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL); + + if (entity != null) + { +@@ -109,21 +111,21 @@ + + public boolean trySpawnPortal(World worldIn, BlockPos pos) + { +- BlockPortal.Size blockportal$size = new BlockPortal.Size(worldIn, pos, EnumFacing.Axis.X); ++ Size blockportal$size = new Size(worldIn, pos, EnumFacing.Axis.X); + +- if (blockportal$size.isValid() && blockportal$size.portalBlockCount == 0 && !net.minecraftforge.event.ForgeEventFactory.onTrySpawnPortal(worldIn, pos, blockportal$size)) ++ if (blockportal$size.isValid() && blockportal$size.portalBlockCount == 0 && !net.minecraftforge.event.ForgeEventFactory.onTrySpawnPortal(worldIn,pos,blockportal$size)) + { +- blockportal$size.placePortalBlocks(); +- return true; ++ return blockportal$size.placePortalBlocksCB(); ++// return true; + } + else + { +- BlockPortal.Size blockportal$size1 = new BlockPortal.Size(worldIn, pos, EnumFacing.Axis.Z); ++ Size blockportal$size1 = new Size(worldIn, pos, EnumFacing.Axis.Z); + +- if (blockportal$size1.isValid() && blockportal$size1.portalBlockCount == 0 && !net.minecraftforge.event.ForgeEventFactory.onTrySpawnPortal(worldIn, pos, blockportal$size1)) ++ if (blockportal$size1.isValid() && blockportal$size1.portalBlockCount == 0 && !net.minecraftforge.event.ForgeEventFactory.onTrySpawnPortal(worldIn,pos,blockportal$size1)) + { +- blockportal$size1.placePortalBlocks(); +- return true; ++ return blockportal$size1.placePortalBlocksCB(); ++// return true; + } + else + { +@@ -138,7 +140,7 @@ + + if (enumfacing$axis == EnumFacing.Axis.X) + { +- BlockPortal.Size blockportal$size = new BlockPortal.Size(worldIn, pos, EnumFacing.Axis.X); ++ Size blockportal$size = new Size(worldIn, pos, EnumFacing.Axis.X); + + if (!blockportal$size.isValid() || blockportal$size.portalBlockCount < blockportal$size.width * blockportal$size.height) + { +@@ -147,7 +149,7 @@ + } + else if (enumfacing$axis == EnumFacing.Axis.Z) + { +- BlockPortal.Size blockportal$size1 = new BlockPortal.Size(worldIn, pos, EnumFacing.Axis.Z); ++ Size blockportal$size1 = new Size(worldIn, pos, EnumFacing.Axis.Z); + + if (!blockportal$size1.isValid() || blockportal$size1.portalBlockCount < blockportal$size1.width * blockportal$size1.height) + { +@@ -216,6 +218,8 @@ + { + if (!entityIn.isRiding() && !entityIn.isBeingRidden() && entityIn.isNonBoss()) + { ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entityIn.getBukkitEntity(), new org.bukkit.Location(worldIn.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent(event); + entityIn.setPortal(pos); + } + } +@@ -304,13 +308,13 @@ + public BlockPattern.PatternHelper createPatternHelper(World worldIn, BlockPos p_181089_2_) + { + EnumFacing.Axis enumfacing$axis = EnumFacing.Axis.Z; +- BlockPortal.Size blockportal$size = new BlockPortal.Size(worldIn, p_181089_2_, EnumFacing.Axis.X); ++ Size blockportal$size = new Size(worldIn, p_181089_2_, EnumFacing.Axis.X); + LoadingCache loadingcache = BlockPattern.createLoadingCache(worldIn, true); + + if (!blockportal$size.isValid()) + { + enumfacing$axis = EnumFacing.Axis.X; +- blockportal$size = new BlockPortal.Size(worldIn, p_181089_2_, EnumFacing.Axis.Z); ++ blockportal$size = new Size(worldIn, p_181089_2_, EnumFacing.Axis.Z); + } + + if (!blockportal$size.isValid()) +@@ -370,6 +374,7 @@ + private BlockPos bottomLeft; + private int height; + private int width; ++ java.util.Collection blocks = new java.util.HashSet(); + + public Size(World worldIn, BlockPos p_i45694_2_, EnumFacing.Axis p_i45694_3_) + { +@@ -442,6 +447,8 @@ + + protected int calculatePortalHeight() + { ++ this.blocks.clear(); ++ org.bukkit.World bworld = this.world.getWorld(); + label56: + + for (this.height = 0; this.height < 21; ++this.height) +@@ -468,6 +475,9 @@ + if (block != Blocks.OBSIDIAN) + { + break label56; ++ } else { ++ BlockPos pos = blockpos.offset(this.leftDir); ++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ())); + } + } + else if (i == this.width - 1) +@@ -477,6 +487,9 @@ + if (block != Blocks.OBSIDIAN) + { + break label56; ++ } else { ++ BlockPos pos = blockpos.offset(this.rightDir); ++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ())); + } + } + } +@@ -488,6 +501,9 @@ + { + this.height = 0; + break; ++ } else { ++ BlockPos pos = this.bottomLeft.offset(this.rightDir, j).up(this.height); ++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ())); + } + } + +@@ -526,5 +542,38 @@ + } + } + } ++ ++ public boolean placePortalBlocksCB() ++ { ++ org.bukkit.World bworld = this.world.getWorld(); ++ ++ // Copy below for loop ++ for (int i = 0; i < this.width; ++i) { ++ BlockPos blockposition = this.bottomLeft.offset(this.rightDir, i); ++ ++ for (int j = 0; j < this.height; ++j) { ++ BlockPos pos = blockposition.up(j); ++ blocks.add(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ } ++ } ++ ++ PortalCreateEvent event = new PortalCreateEvent(blocks, bworld, PortalCreateEvent.CreateReason.FIRE); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ for (int i = 0; i < this.width; ++i) ++ { ++ BlockPos blockpos = this.bottomLeft.offset(this.rightDir, i); ++ ++ for (int j = 0; j < this.height; ++j) ++ { ++ this.world.setBlockState(blockpos.up(j), Blocks.PORTAL.getDefaultState().withProperty(BlockPortal.AXIS, this.axis), 2); ++ } ++ } ++ ++ return true; ++ } + } + } diff --git a/patches/net/minecraft/block/BlockPressurePlate.java.patch b/patches/net/minecraft/block/BlockPressurePlate.java.patch new file mode 100644 index 00000000..4da9c700 --- /dev/null +++ b/patches/net/minecraft/block/BlockPressurePlate.java.patch @@ -0,0 +1,44 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockPressurePlate.java ++++ ../src-work/minecraft/net/minecraft/block/BlockPressurePlate.java +@@ -14,13 +14,14 @@ + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.event.entity.EntityInteractEvent; + + public class BlockPressurePlate extends BlockBasePressurePlate + { + public static final PropertyBool POWERED = PropertyBool.create("powered"); +- private final BlockPressurePlate.Sensitivity sensitivity; ++ private final Sensitivity sensitivity; + +- protected BlockPressurePlate(Material materialIn, BlockPressurePlate.Sensitivity sensitivityIn) ++ protected BlockPressurePlate(Material materialIn, Sensitivity sensitivityIn) + { + super(materialIn); + this.setDefaultState(this.blockState.getBaseState().withProperty(POWERED, Boolean.valueOf(false))); +@@ -82,6 +83,24 @@ + { + for (Entity entity : list) + { ++ if (this.getRedstoneStrength(worldIn.getBlockState(pos)) == 0) { ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.plugin.PluginManager manager = worldIn.getServer().getPluginManager(); ++ org.bukkit.event.Cancellable cancellable; ++ ++ if (entity instanceof EntityPlayer) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityPlayer) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ manager.callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ // We only want to block turning the plate on if all events are cancelled ++ if (cancellable.isCancelled()) { ++ continue; ++ } ++ } ++ + if (!entity.doesEntityNotTriggerPressurePlate()) + { + return 15; diff --git a/patches/net/minecraft/block/BlockPressurePlateWeighted.java.patch b/patches/net/minecraft/block/BlockPressurePlateWeighted.java.patch new file mode 100644 index 00000000..33677720 --- /dev/null +++ b/patches/net/minecraft/block/BlockPressurePlateWeighted.java.patch @@ -0,0 +1,42 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockPressurePlateWeighted.java ++++ ../src-work/minecraft/net/minecraft/block/BlockPressurePlateWeighted.java +@@ -13,6 +13,7 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; ++import org.bukkit.event.entity.EntityInteractEvent; + + public class BlockPressurePlateWeighted extends BlockBasePressurePlate + { +@@ -33,8 +34,30 @@ + + protected int computeRedstoneStrength(World worldIn, BlockPos pos) + { +- int i = Math.min(worldIn.getEntitiesWithinAABB(Entity.class, PRESSURE_AABB.offset(pos)).size(), this.maxWeight); ++// int i = Math.min(worldIn.getEntitiesWithinAABB(Entity.class, PRESSURE_AABB.offset(pos)).size(), this.maxWeight); ++ int i = 0; ++ java.util.Iterator iterator = worldIn.getEntitiesWithinAABB(Entity.class, BlockPressurePlateWeighted.PRESSURE_AABB.offset(pos)).iterator(); + ++ while (iterator.hasNext()) { ++ Entity entity = (Entity) iterator.next(); ++ ++ org.bukkit.event.Cancellable cancellable; ++ ++ if (entity instanceof EntityPlayer) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityPlayer) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ // We only want to block turning the plate on if all events are cancelled ++ if (!cancellable.isCancelled()) { ++ i++; ++ } ++ } ++ ++ i = Math.min(i, this.maxWeight); ++ + if (i > 0) + { + float f = (float)Math.min(this.maxWeight, i) / (float)this.maxWeight; diff --git a/patches/net/minecraft/block/BlockPumpkin.java.patch b/patches/net/minecraft/block/BlockPumpkin.java.patch new file mode 100644 index 00000000..0b4c49c8 --- /dev/null +++ b/patches/net/minecraft/block/BlockPumpkin.java.patch @@ -0,0 +1,111 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockPumpkin.java ++++ ../src-work/minecraft/net/minecraft/block/BlockPumpkin.java +@@ -25,6 +25,8 @@ + import net.minecraft.util.Rotation; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.util.BlockStateListPopulator; ++import org.bukkit.event.entity.CreatureSpawnEvent; + + public class BlockPumpkin extends BlockHorizontal + { +@@ -62,34 +64,35 @@ + private void trySpawnGolem(World worldIn, BlockPos pos) + { + BlockPattern.PatternHelper blockpattern$patternhelper = this.getSnowmanPattern().match(worldIn, pos); +- ++ BlockStateListPopulator blockList = new BlockStateListPopulator(worldIn.getWorld()); + if (blockpattern$patternhelper != null) + { + for (int i = 0; i < this.getSnowmanPattern().getThumbLength(); ++i) + { + BlockWorldState blockworldstate = blockpattern$patternhelper.translateOffset(0, i, 0); +- worldIn.setBlockState(blockworldstate.getPos(), Blocks.AIR.getDefaultState(), 2); ++// worldIn.setBlockState(blockworldstate.getPos(), Blocks.AIR.getDefaultState(), 2); ++ BlockPos pos1 = blockworldstate.getPos(); ++ blockList.setTypeId(pos1.getX(), pos1.getY(), pos1.getZ(), 0); + } + + EntitySnowman entitysnowman = new EntitySnowman(worldIn); + BlockPos blockpos1 = blockpattern$patternhelper.translateOffset(0, 2, 0).getPos(); + entitysnowman.setLocationAndAngles((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.05D, (double)blockpos1.getZ() + 0.5D, 0.0F, 0.0F); +- worldIn.spawnEntity(entitysnowman); ++ if (worldIn.spawnEntity(entitysnowman, CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN)) { ++ blockList.updateList(); + +- for (EntityPlayerMP entityplayermp : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entitysnowman.getEntityBoundingBox().grow(5.0D))) +- { +- CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp, entitysnowman); +- } ++ for (EntityPlayerMP entityplayermp : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entitysnowman.getEntityBoundingBox().grow(5.0D))) { ++ CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp, entitysnowman); ++ } + +- for (int l = 0; l < 120; ++l) +- { +- worldIn.spawnParticle(EnumParticleTypes.SNOW_SHOVEL, (double)blockpos1.getX() + worldIn.rand.nextDouble(), (double)blockpos1.getY() + worldIn.rand.nextDouble() * 2.5D, (double)blockpos1.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); +- } ++ for (int l = 0; l < 120; ++l) { ++ worldIn.spawnParticle(EnumParticleTypes.SNOW_SHOVEL, (double) blockpos1.getX() + worldIn.rand.nextDouble(), (double) blockpos1.getY() + worldIn.rand.nextDouble() * 2.5D, (double) blockpos1.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); ++ } + +- for (int i1 = 0; i1 < this.getSnowmanPattern().getThumbLength(); ++i1) +- { +- BlockWorldState blockworldstate2 = blockpattern$patternhelper.translateOffset(0, i1, 0); +- worldIn.notifyNeighborsRespectDebug(blockworldstate2.getPos(), Blocks.AIR, false); ++ for (int i1 = 0; i1 < this.getSnowmanPattern().getThumbLength(); ++i1) { ++ BlockWorldState blockworldstate2 = blockpattern$patternhelper.translateOffset(0, i1, 0); ++ worldIn.notifyNeighborsRespectDebug(blockworldstate2.getPos(), Blocks.AIR, false); ++ } + } + } + else +@@ -102,7 +105,9 @@ + { + for (int k = 0; k < this.getGolemPattern().getThumbLength(); ++k) + { +- worldIn.setBlockState(blockpattern$patternhelper.translateOffset(j, k, 0).getPos(), Blocks.AIR.getDefaultState(), 2); ++// worldIn.setBlockState(blockpattern$patternhelper.translateOffset(j, k, 0).getPos(), Blocks.AIR.getDefaultState(), 2); ++ BlockPos pos2 = blockpattern$patternhelper.translateOffset(j, k, 0).getPos(); ++ blockList.setTypeId(pos2.getX(), pos2.getY(), pos2.getZ(), 0); + } + } + +@@ -110,24 +115,22 @@ + EntityIronGolem entityirongolem = new EntityIronGolem(worldIn); + entityirongolem.setPlayerCreated(true); + entityirongolem.setLocationAndAngles((double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 0.05D, (double)blockpos.getZ() + 0.5D, 0.0F, 0.0F); +- worldIn.spawnEntity(entityirongolem); ++ if (worldIn.spawnEntity(entityirongolem, CreatureSpawnEvent.SpawnReason.BUILD_IRONGOLEM)) { ++ blockList.updateList(); + +- for (EntityPlayerMP entityplayermp1 : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entityirongolem.getEntityBoundingBox().grow(5.0D))) +- { +- CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp1, entityirongolem); +- } ++ for (EntityPlayerMP entityplayermp1 : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entityirongolem.getEntityBoundingBox().grow(5.0D))) { ++ CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp1, entityirongolem); ++ } + +- for (int j1 = 0; j1 < 120; ++j1) +- { +- worldIn.spawnParticle(EnumParticleTypes.SNOWBALL, (double)blockpos.getX() + worldIn.rand.nextDouble(), (double)blockpos.getY() + worldIn.rand.nextDouble() * 3.9D, (double)blockpos.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); +- } ++ for (int j1 = 0; j1 < 120; ++j1) { ++ worldIn.spawnParticle(EnumParticleTypes.SNOWBALL, (double) blockpos.getX() + worldIn.rand.nextDouble(), (double) blockpos.getY() + worldIn.rand.nextDouble() * 3.9D, (double) blockpos.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); ++ } + +- for (int k1 = 0; k1 < this.getGolemPattern().getPalmLength(); ++k1) +- { +- for (int l1 = 0; l1 < this.getGolemPattern().getThumbLength(); ++l1) +- { +- BlockWorldState blockworldstate1 = blockpattern$patternhelper.translateOffset(k1, l1, 0); +- worldIn.notifyNeighborsRespectDebug(blockworldstate1.getPos(), Blocks.AIR, false); ++ for (int k1 = 0; k1 < this.getGolemPattern().getPalmLength(); ++k1) { ++ for (int l1 = 0; l1 < this.getGolemPattern().getThumbLength(); ++l1) { ++ BlockWorldState blockworldstate1 = blockpattern$patternhelper.translateOffset(k1, l1, 0); ++ worldIn.notifyNeighborsRespectDebug(blockworldstate1.getPos(), Blocks.AIR, false); ++ } + } + } + } diff --git a/patches/net/minecraft/block/BlockRailDetector.java.patch b/patches/net/minecraft/block/BlockRailDetector.java.patch new file mode 100644 index 00000000..6f4786d2 --- /dev/null +++ b/patches/net/minecraft/block/BlockRailDetector.java.patch @@ -0,0 +1,250 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRailDetector.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRailDetector.java +@@ -22,14 +22,18 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.event.block.BlockRedstoneEvent; + ++import net.minecraft.block.BlockRailBase.EnumRailDirection; ++import net.minecraft.block.BlockRailBase.Rail; ++ + public class BlockRailDetector extends BlockRailBase + { +- public static final PropertyEnum SHAPE = PropertyEnum.create("shape", BlockRailBase.EnumRailDirection.class, new Predicate() ++ public static final PropertyEnum SHAPE = PropertyEnum.create("shape", EnumRailDirection.class, new Predicate() + { +- public boolean apply(@Nullable BlockRailBase.EnumRailDirection p_apply_1_) ++ public boolean apply(@Nullable EnumRailDirection p_apply_1_) + { +- return p_apply_1_ != BlockRailBase.EnumRailDirection.NORTH_EAST && p_apply_1_ != BlockRailBase.EnumRailDirection.NORTH_WEST && p_apply_1_ != BlockRailBase.EnumRailDirection.SOUTH_EAST && p_apply_1_ != BlockRailBase.EnumRailDirection.SOUTH_WEST; ++ return p_apply_1_ != EnumRailDirection.NORTH_EAST && p_apply_1_ != EnumRailDirection.NORTH_WEST && p_apply_1_ != EnumRailDirection.SOUTH_EAST && p_apply_1_ != EnumRailDirection.SOUTH_WEST; + } + }); + public static final PropertyBool POWERED = PropertyBool.create("powered"); +@@ -37,7 +41,7 @@ + public BlockRailDetector() + { + super(true); +- this.setDefaultState(this.blockState.getBaseState().withProperty(POWERED, Boolean.valueOf(false)).withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH)); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(POWERED, Boolean.valueOf(false)).withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH)); + this.setTickRandomly(true); + } + +@@ -102,6 +106,13 @@ + flag1 = true; + } + ++ if (flag != flag1) { ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, flag ? 15 : 0, flag1 ? 15 : 0); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ } ++ + if (flag1 && !flag) + { + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(true)), 3); +@@ -130,7 +141,7 @@ + + protected void updateConnectedRails(World worldIn, BlockPos pos, IBlockState state, boolean powered) + { +- BlockRailBase.Rail blockrailbase$rail = new BlockRailBase.Rail(worldIn, pos, state); ++ Rail blockrailbase$rail = new Rail(worldIn, pos, state); + + for (BlockPos blockpos : blockrailbase$rail.getConnectedRails()) + { +@@ -149,7 +160,7 @@ + this.updatePoweredState(worldIn, pos, state); + } + +- public IProperty getShapeProperty() ++ public IProperty getShapeProperty() + { + return SHAPE; + } +@@ -197,13 +208,13 @@ + + public IBlockState getStateFromMeta(int meta) + { +- return this.getDefaultState().withProperty(SHAPE, BlockRailBase.EnumRailDirection.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); ++ return this.getDefaultState().withProperty(SHAPE, EnumRailDirection.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); + } + + public int getMetaFromState(IBlockState state) + { + int i = 0; +- i = i | ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)).getMetadata(); ++ i = i | ((EnumRailDirection)state.getValue(SHAPE)).getMetadata(); + + if (((Boolean)state.getValue(POWERED)).booleanValue()) + { +@@ -220,76 +231,76 @@ + { + case CLOCKWISE_180: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + } + + case COUNTERCLOCKWISE_90: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case NORTH_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.EAST_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.EAST_WEST); + case EAST_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH); + } + + case CLOCKWISE_90: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.EAST_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.EAST_WEST); + case EAST_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH); + } + + default: +@@ -300,7 +311,7 @@ + @SuppressWarnings("incomplete-switch") + public IBlockState withMirror(IBlockState state, Mirror mirrorIn) + { +- BlockRailBase.EnumRailDirection blockrailbase$enumraildirection = (BlockRailBase.EnumRailDirection)state.getValue(SHAPE); ++ EnumRailDirection blockrailbase$enumraildirection = (EnumRailDirection)state.getValue(SHAPE); + + switch (mirrorIn) + { +@@ -309,17 +320,17 @@ + switch (blockrailbase$enumraildirection) + { + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + default: + return super.withMirror(state, mirrorIn); + } +@@ -329,21 +340,21 @@ + switch (blockrailbase$enumraildirection) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_NORTH: + case ASCENDING_SOUTH: + default: + break; + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + } + } + diff --git a/patches/net/minecraft/block/BlockRailPowered.java.patch b/patches/net/minecraft/block/BlockRailPowered.java.patch new file mode 100644 index 00000000..23179db4 --- /dev/null +++ b/patches/net/minecraft/block/BlockRailPowered.java.patch @@ -0,0 +1,310 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRailPowered.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRailPowered.java +@@ -11,14 +11,17 @@ + import net.minecraft.util.Rotation; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + ++import net.minecraft.block.BlockRailBase.EnumRailDirection; ++ + public class BlockRailPowered extends BlockRailBase + { +- public static final PropertyEnum SHAPE = PropertyEnum.create("shape", BlockRailBase.EnumRailDirection.class, new Predicate() ++ public static final PropertyEnum SHAPE = PropertyEnum.create("shape", EnumRailDirection.class, new Predicate() + { +- public boolean apply(@Nullable BlockRailBase.EnumRailDirection p_apply_1_) ++ public boolean apply(@Nullable EnumRailDirection p_apply_1_) + { +- return p_apply_1_ != BlockRailBase.EnumRailDirection.NORTH_EAST && p_apply_1_ != BlockRailBase.EnumRailDirection.NORTH_WEST && p_apply_1_ != BlockRailBase.EnumRailDirection.SOUTH_EAST && p_apply_1_ != BlockRailBase.EnumRailDirection.SOUTH_WEST; ++ return p_apply_1_ != EnumRailDirection.NORTH_EAST && p_apply_1_ != EnumRailDirection.NORTH_WEST && p_apply_1_ != EnumRailDirection.SOUTH_EAST && p_apply_1_ != EnumRailDirection.SOUTH_WEST; + } + }); + public static final PropertyBool POWERED = PropertyBool.create("powered"); +@@ -34,7 +37,7 @@ + { + super(true); + this.isActivator = isActivator; +- this.setDefaultState(this.blockState.getBaseState().withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH).withProperty(POWERED, Boolean.valueOf(false))); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH).withProperty(POWERED, Boolean.valueOf(false))); + } + + @SuppressWarnings("incomplete-switch") +@@ -50,7 +53,7 @@ + int j = pos.getY(); + int k = pos.getZ(); + boolean flag = true; +- BlockRailBase.EnumRailDirection blockrailbase$enumraildirection = (BlockRailBase.EnumRailDirection)state.getValue(SHAPE); ++ EnumRailDirection blockrailbase$enumraildirection = (EnumRailDirection)state.getValue(SHAPE); + + switch (blockrailbase$enumraildirection) + { +@@ -91,7 +94,7 @@ + flag = false; + } + +- blockrailbase$enumraildirection = BlockRailBase.EnumRailDirection.EAST_WEST; ++ blockrailbase$enumraildirection = EnumRailDirection.EAST_WEST; + break; + case ASCENDING_WEST: + +@@ -106,7 +109,7 @@ + ++i; + } + +- blockrailbase$enumraildirection = BlockRailBase.EnumRailDirection.EAST_WEST; ++ blockrailbase$enumraildirection = EnumRailDirection.EAST_WEST; + break; + case ASCENDING_NORTH: + +@@ -121,7 +124,7 @@ + flag = false; + } + +- blockrailbase$enumraildirection = BlockRailBase.EnumRailDirection.NORTH_SOUTH; ++ blockrailbase$enumraildirection = EnumRailDirection.NORTH_SOUTH; + break; + case ASCENDING_SOUTH: + +@@ -136,7 +139,7 @@ + --k; + } + +- blockrailbase$enumraildirection = BlockRailBase.EnumRailDirection.NORTH_SOUTH; ++ blockrailbase$enumraildirection = EnumRailDirection.NORTH_SOUTH; + } + + if (this.isSameRailWithPower(worldIn, new BlockPos(i, j, k), p_176566_4_, p_176566_5_, blockrailbase$enumraildirection)) +@@ -150,7 +153,7 @@ + } + } + +- protected boolean isSameRailWithPower(World worldIn, BlockPos pos, boolean p_176567_3_, int distance, BlockRailBase.EnumRailDirection p_176567_5_) ++ protected boolean isSameRailWithPower(World worldIn, BlockPos pos, boolean p_176567_3_, int distance, EnumRailDirection p_176567_5_) + { + IBlockState iblockstate = worldIn.getBlockState(pos); + +@@ -160,11 +163,11 @@ + } + else + { +- BlockRailBase.EnumRailDirection blockrailbase$enumraildirection = (BlockRailBase.EnumRailDirection)iblockstate.getValue(SHAPE); ++ EnumRailDirection blockrailbase$enumraildirection = (EnumRailDirection)iblockstate.getValue(SHAPE); + +- if (p_176567_5_ != BlockRailBase.EnumRailDirection.EAST_WEST || blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.NORTH_SOUTH && blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.ASCENDING_NORTH && blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.ASCENDING_SOUTH) ++ if (p_176567_5_ != EnumRailDirection.EAST_WEST || blockrailbase$enumraildirection != EnumRailDirection.NORTH_SOUTH && blockrailbase$enumraildirection != EnumRailDirection.ASCENDING_NORTH && blockrailbase$enumraildirection != EnumRailDirection.ASCENDING_SOUTH) + { +- if (p_176567_5_ != BlockRailBase.EnumRailDirection.NORTH_SOUTH || blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.EAST_WEST && blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.ASCENDING_EAST && blockrailbase$enumraildirection != BlockRailBase.EnumRailDirection.ASCENDING_WEST) ++ if (p_176567_5_ != EnumRailDirection.NORTH_SOUTH || blockrailbase$enumraildirection != EnumRailDirection.EAST_WEST && blockrailbase$enumraildirection != EnumRailDirection.ASCENDING_EAST && blockrailbase$enumraildirection != EnumRailDirection.ASCENDING_WEST) + { + if (((Boolean)iblockstate.getValue(POWERED)).booleanValue()) + { +@@ -194,30 +197,35 @@ + + if (flag1 != flag) + { ++ int power = state.getValue(POWERED) ? 15 : 0; ++ int newPower = CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), power, 15 - power).getNewCurrent(); ++ if (newPower == power) { ++ return; ++ } + worldIn.setBlockState(pos, state.withProperty(POWERED, Boolean.valueOf(flag1)), 3); + worldIn.notifyNeighborsOfStateChange(pos.down(), this, false); + +- if (((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)).isAscending()) ++ if (((EnumRailDirection)state.getValue(SHAPE)).isAscending()) + { + worldIn.notifyNeighborsOfStateChange(pos.up(), this, false); + } + } + } + +- public IProperty getShapeProperty() ++ public IProperty getShapeProperty() + { + return SHAPE; + } + + public IBlockState getStateFromMeta(int meta) + { +- return this.getDefaultState().withProperty(SHAPE, BlockRailBase.EnumRailDirection.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); ++ return this.getDefaultState().withProperty(SHAPE, EnumRailDirection.byMetadata(meta & 7)).withProperty(POWERED, Boolean.valueOf((meta & 8) > 0)); + } + + public int getMetaFromState(IBlockState state) + { + int i = 0; +- i = i | ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)).getMetadata(); ++ i = i | ((EnumRailDirection)state.getValue(SHAPE)).getMetadata(); + + if (((Boolean)state.getValue(POWERED)).booleanValue()) + { +@@ -234,76 +242,76 @@ + { + case CLOCKWISE_180: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + } + + case COUNTERCLOCKWISE_90: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case NORTH_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.EAST_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.EAST_WEST); + case EAST_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH); + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + } + + case CLOCKWISE_90: + +- switch ((BlockRailBase.EnumRailDirection)state.getValue(SHAPE)) ++ switch ((EnumRailDirection)state.getValue(SHAPE)) + { + case NORTH_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.EAST_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.EAST_WEST); + case EAST_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_SOUTH); + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + } + + default: +@@ -314,7 +322,7 @@ + @SuppressWarnings("incomplete-switch") + public IBlockState withMirror(IBlockState state, Mirror mirrorIn) + { +- BlockRailBase.EnumRailDirection blockrailbase$enumraildirection = (BlockRailBase.EnumRailDirection)state.getValue(SHAPE); ++ EnumRailDirection blockrailbase$enumraildirection = (EnumRailDirection)state.getValue(SHAPE); + + switch (mirrorIn) + { +@@ -323,17 +331,17 @@ + switch (blockrailbase$enumraildirection) + { + case ASCENDING_NORTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_SOUTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_SOUTH); + case ASCENDING_SOUTH: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_NORTH); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_NORTH); + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + default: + return super.withMirror(state, mirrorIn); + } +@@ -343,21 +351,21 @@ + switch (blockrailbase$enumraildirection) + { + case ASCENDING_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_WEST); + case ASCENDING_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.ASCENDING_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.ASCENDING_EAST); + case ASCENDING_NORTH: + case ASCENDING_SOUTH: + default: + break; + case SOUTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_WEST); + case SOUTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.SOUTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.SOUTH_EAST); + case NORTH_WEST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_EAST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_EAST); + case NORTH_EAST: +- return state.withProperty(SHAPE, BlockRailBase.EnumRailDirection.NORTH_WEST); ++ return state.withProperty(SHAPE, EnumRailDirection.NORTH_WEST); + } + } + diff --git a/patches/net/minecraft/block/BlockRedstoneDiode.java.patch b/patches/net/minecraft/block/BlockRedstoneDiode.java.patch new file mode 100644 index 00000000..52b01c27 --- /dev/null +++ b/patches/net/minecraft/block/BlockRedstoneDiode.java.patch @@ -0,0 +1,27 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRedstoneDiode.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRedstoneDiode.java +@@ -15,6 +15,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public abstract class BlockRedstoneDiode extends BlockHorizontal + { +@@ -61,10 +62,16 @@ + + if (this.isRepeaterPowered && !flag) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 15, 0).getNewCurrent() != 0) { ++ return; ++ } + worldIn.setBlockState(pos, this.getUnpoweredState(state), 2); + } + else if (!this.isRepeaterPowered) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 0, 15).getNewCurrent() != 15) { ++ return; ++ } + worldIn.setBlockState(pos, this.getPoweredState(state), 2); + + if (!flag) diff --git a/patches/net/minecraft/block/BlockRedstoneLight.java.patch b/patches/net/minecraft/block/BlockRedstoneLight.java.patch new file mode 100644 index 00000000..1b9241c1 --- /dev/null +++ b/patches/net/minecraft/block/BlockRedstoneLight.java.patch @@ -0,0 +1,47 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRedstoneLight.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRedstoneLight.java +@@ -8,6 +8,7 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockRedstoneLight extends Block + { +@@ -30,10 +31,16 @@ + { + if (this.isOn && !worldIn.isBlockPowered(pos)) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 15, 0).getNewCurrent() != 0) { ++ return; ++ } + worldIn.setBlockState(pos, Blocks.REDSTONE_LAMP.getDefaultState(), 2); + } + else if (!this.isOn && worldIn.isBlockPowered(pos)) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 0, 15).getNewCurrent() != 15) { ++ return; ++ } + worldIn.setBlockState(pos, Blocks.LIT_REDSTONE_LAMP.getDefaultState(), 2); + } + } +@@ -49,6 +56,9 @@ + } + else if (!this.isOn && worldIn.isBlockPowered(pos)) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 0, 15).getNewCurrent() != 15) { ++ return; ++ } + worldIn.setBlockState(pos, Blocks.LIT_REDSTONE_LAMP.getDefaultState(), 2); + } + } +@@ -60,6 +70,9 @@ + { + if (this.isOn && !worldIn.isBlockPowered(pos)) + { ++ if (CraftEventFactory.callRedstoneChange(worldIn, pos.getX(), pos.getY(), pos.getZ(), 15, 0).getNewCurrent() != 0) { ++ return; ++ } + worldIn.setBlockState(pos, Blocks.REDSTONE_LAMP.getDefaultState(), 2); + } + } diff --git a/patches/net/minecraft/block/BlockRedstoneOre.java.patch b/patches/net/minecraft/block/BlockRedstoneOre.java.patch new file mode 100644 index 00000000..014618b4 --- /dev/null +++ b/patches/net/minecraft/block/BlockRedstoneOre.java.patch @@ -0,0 +1,76 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRedstoneOre.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRedstoneOre.java +@@ -16,6 +16,8 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityInteractEvent; + + public class BlockRedstoneOre extends Block + { +@@ -40,19 +42,33 @@ + + public void onBlockClicked(World worldIn, BlockPos pos, EntityPlayer playerIn) + { +- this.activate(worldIn, pos); ++ this.activate(worldIn, pos, playerIn); // CraftBukkit - add playerIn + super.onBlockClicked(worldIn, pos, playerIn); + } + + public void onEntityWalk(World worldIn, BlockPos pos, Entity entityIn) + { +- this.activate(worldIn, pos); +- super.onEntityWalk(worldIn, pos, entityIn); ++// this.activate(worldIn, pos); ++// super.onEntityWalk(worldIn, pos, entityIn); ++ if (entityIn instanceof EntityPlayer) { ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityPlayer) entityIn, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ if (!event.isCancelled()) { ++ this.activate(worldIn, pos, entityIn); // add entity ++ super.onEntityWalk(worldIn, pos, entityIn); ++ } ++ } else { ++ EntityInteractEvent event = new EntityInteractEvent(entityIn.getBukkitEntity(), worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.activate(worldIn, pos, entityIn); // add entity ++ super.onEntityWalk(worldIn, pos, entityIn); ++ } ++ } + } + + public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) + { +- this.activate(worldIn, pos); ++ this.activate(worldIn, pos, playerIn); // CraftBukkit - add playerIn + return super.onBlockActivated(worldIn, pos, state, playerIn, hand, facing, hitX, hitY, hitZ); + } + +@@ -66,10 +82,26 @@ + } + } + ++ private void activate(World worldIn, BlockPos pos, Entity entity) ++ { ++ this.spawnParticles(worldIn, pos); ++ ++ if (this == Blocks.REDSTONE_ORE) ++ { ++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.LIT_REDSTONE_ORE, 0).isCancelled()) { ++ return; ++ } ++ worldIn.setBlockState(pos, Blocks.LIT_REDSTONE_ORE.getDefaultState()); ++ } ++ } ++ + public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand) + { + if (this == Blocks.LIT_REDSTONE_ORE) + { ++ if (CraftEventFactory.callBlockFadeEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), Blocks.REDSTONE_ORE).isCancelled()) { ++ return; ++ } + worldIn.setBlockState(pos, Blocks.REDSTONE_ORE.getDefaultState()); + } + } diff --git a/patches/net/minecraft/block/BlockRedstoneTorch.java.patch b/patches/net/minecraft/block/BlockRedstoneTorch.java.patch new file mode 100644 index 00000000..4fa3aa9a --- /dev/null +++ b/patches/net/minecraft/block/BlockRedstoneTorch.java.patch @@ -0,0 +1,109 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRedstoneTorch.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRedstoneTorch.java +@@ -20,10 +20,11 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockRedstoneTorch extends BlockTorch + { +- private static final Map> toggles = new java.util.WeakHashMap>(); // FORGE - fix vanilla MC-101233 ++ private static final Map> toggles = new java.util.WeakHashMap>(); // FORGE - fix vanilla MC-101233 + private final boolean isOn; + + private boolean isBurnedOut(World worldIn, BlockPos pos, boolean turnOff) +@@ -33,18 +34,18 @@ + toggles.put(worldIn, Lists.newArrayList()); + } + +- List list = (List)toggles.get(worldIn); ++ List list = (List)toggles.get(worldIn); + + if (turnOff) + { +- list.add(new BlockRedstoneTorch.Toggle(pos, worldIn.getTotalWorldTime())); ++ list.add(new Toggle(pos, worldIn.getTotalWorldTime())); + } + + int i = 0; + + for (int j = 0; j < list.size(); ++j) + { +- BlockRedstoneTorch.Toggle blockredstonetorch$toggle = list.get(j); ++ Toggle blockredstonetorch$toggle = list.get(j); + + if (blockredstonetorch$toggle.pos.equals(pos)) + { +@@ -109,20 +110,46 @@ + { + } + ++ private long last = 0L; + public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand) + { ++ if (System.currentTimeMillis() - last <= -1) ++ { ++ return; ++ } ++ last = System.currentTimeMillis(); + boolean flag = this.shouldBeOff(worldIn, pos, state); + List list = (List)toggles.get(worldIn); + +- while (list != null && !list.isEmpty() && worldIn.getTotalWorldTime() - (list.get(0)).time > 60L) +- { +- list.remove(0); ++ // Paper start ++ if (list != null) { ++ int index = 0; ++ while (index < list.size() && worldIn.getTotalWorldTime() - list.get(index).getTime() > 60L) { ++ index++; ++ } ++ if (index > 0) { ++ list.subList(0, index).clear(); + } ++ } ++ // Paper end + ++ org.bukkit.plugin.PluginManager manager = worldIn.getServer().getPluginManager(); ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int oldCurrent = this.isOn ? 15 : 0; ++ ++ BlockRedstoneEvent event = new BlockRedstoneEvent(block, oldCurrent, oldCurrent); ++ + if (this.isOn) + { + if (flag) + { ++ if (oldCurrent != 0) { ++ event.setNewCurrent(0); ++ manager.callEvent(event); ++ if (event.getNewCurrent() != 0) { ++ return; ++ } ++ } + worldIn.setBlockState(pos, Blocks.UNLIT_REDSTONE_TORCH.getDefaultState().withProperty(FACING, state.getValue(FACING)), 3); + + if (this.isBurnedOut(worldIn, pos, true)) +@@ -143,6 +170,13 @@ + } + else if (!flag && !this.isBurnedOut(worldIn, pos, false)) + { ++ if (oldCurrent != 15) { ++ event.setNewCurrent(15); ++ manager.callEvent(event); ++ if (event.getNewCurrent() != 15) { ++ return; ++ } ++ } + worldIn.setBlockState(pos, Blocks.REDSTONE_TORCH.getDefaultState().withProperty(FACING, state.getValue(FACING)), 3); + } + } +@@ -210,6 +244,7 @@ + { + BlockPos pos; + long time; ++ final long getTime() { return this.time; } // Paper - OBFHELPER; + + public Toggle(BlockPos pos, long time) + { diff --git a/patches/net/minecraft/block/BlockRedstoneWire.java.patch b/patches/net/minecraft/block/BlockRedstoneWire.java.patch new file mode 100644 index 00000000..7d6902e1 --- /dev/null +++ b/patches/net/minecraft/block/BlockRedstoneWire.java.patch @@ -0,0 +1,99 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockRedstoneWire.java ++++ ../src-work/minecraft/net/minecraft/block/BlockRedstoneWire.java +@@ -31,13 +31,14 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockRedstoneWire extends Block + { +- public static final PropertyEnum NORTH = PropertyEnum.create("north", BlockRedstoneWire.EnumAttachPosition.class); +- public static final PropertyEnum EAST = PropertyEnum.create("east", BlockRedstoneWire.EnumAttachPosition.class); +- public static final PropertyEnum SOUTH = PropertyEnum.create("south", BlockRedstoneWire.EnumAttachPosition.class); +- public static final PropertyEnum WEST = PropertyEnum.create("west", BlockRedstoneWire.EnumAttachPosition.class); ++ public static final PropertyEnum NORTH = PropertyEnum.create("north", EnumAttachPosition.class); ++ public static final PropertyEnum EAST = PropertyEnum.create("east", EnumAttachPosition.class); ++ public static final PropertyEnum SOUTH = PropertyEnum.create("south", EnumAttachPosition.class); ++ public static final PropertyEnum WEST = PropertyEnum.create("west", EnumAttachPosition.class); + public static final PropertyInteger POWER = PropertyInteger.create("power", 0, 15); + protected static final AxisAlignedBB[] REDSTONE_WIRE_AABB = new AxisAlignedBB[] {new AxisAlignedBB(0.1875D, 0.0D, 0.1875D, 0.8125D, 0.0625D, 0.8125D), new AxisAlignedBB(0.1875D, 0.0D, 0.1875D, 0.8125D, 0.0625D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.1875D, 0.8125D, 0.0625D, 0.8125D), new AxisAlignedBB(0.0D, 0.0D, 0.1875D, 0.8125D, 0.0625D, 1.0D), new AxisAlignedBB(0.1875D, 0.0D, 0.0D, 0.8125D, 0.0625D, 0.8125D), new AxisAlignedBB(0.1875D, 0.0D, 0.0D, 0.8125D, 0.0625D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.8125D, 0.0625D, 0.8125D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.8125D, 0.0625D, 1.0D), new AxisAlignedBB(0.1875D, 0.0D, 0.1875D, 1.0D, 0.0625D, 0.8125D), new AxisAlignedBB(0.1875D, 0.0D, 0.1875D, 1.0D, 0.0625D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.1875D, 1.0D, 0.0625D, 0.8125D), new AxisAlignedBB(0.0D, 0.0D, 0.1875D, 1.0D, 0.0625D, 1.0D), new AxisAlignedBB(0.1875D, 0.0D, 0.0D, 1.0D, 0.0625D, 0.8125D), new AxisAlignedBB(0.1875D, 0.0D, 0.0D, 1.0D, 0.0625D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.0625D, 0.8125D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.0625D, 1.0D)}; + private boolean canProvidePower = true; +@@ -46,7 +47,7 @@ + public BlockRedstoneWire() + { + super(Material.CIRCUITS); +- this.setDefaultState(this.blockState.getBaseState().withProperty(NORTH, BlockRedstoneWire.EnumAttachPosition.NONE).withProperty(EAST, BlockRedstoneWire.EnumAttachPosition.NONE).withProperty(SOUTH, BlockRedstoneWire.EnumAttachPosition.NONE).withProperty(WEST, BlockRedstoneWire.EnumAttachPosition.NONE).withProperty(POWER, Integer.valueOf(0))); ++ this.setDefaultState(this.blockState.getBaseState().withProperty(NORTH, EnumAttachPosition.NONE).withProperty(EAST, EnumAttachPosition.NONE).withProperty(SOUTH, EnumAttachPosition.NONE).withProperty(WEST, EnumAttachPosition.NONE).withProperty(POWER, Integer.valueOf(0))); + } + + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) +@@ -57,10 +58,10 @@ + private static int getAABBIndex(IBlockState state) + { + int i = 0; +- boolean flag = state.getValue(NORTH) != BlockRedstoneWire.EnumAttachPosition.NONE; +- boolean flag1 = state.getValue(EAST) != BlockRedstoneWire.EnumAttachPosition.NONE; +- boolean flag2 = state.getValue(SOUTH) != BlockRedstoneWire.EnumAttachPosition.NONE; +- boolean flag3 = state.getValue(WEST) != BlockRedstoneWire.EnumAttachPosition.NONE; ++ boolean flag = state.getValue(NORTH) != EnumAttachPosition.NONE; ++ boolean flag1 = state.getValue(EAST) != EnumAttachPosition.NONE; ++ boolean flag2 = state.getValue(SOUTH) != EnumAttachPosition.NONE; ++ boolean flag3 = state.getValue(WEST) != EnumAttachPosition.NONE; + + if (flag || flag2 && !flag && !flag1 && !flag3) + { +@@ -94,7 +95,7 @@ + return state; + } + +- private BlockRedstoneWire.EnumAttachPosition getAttachPosition(IBlockAccess worldIn, BlockPos pos, EnumFacing direction) ++ private EnumAttachPosition getAttachPosition(IBlockAccess worldIn, BlockPos pos, EnumFacing direction) + { + BlockPos blockpos = pos.offset(direction); + IBlockState iblockstate = worldIn.getBlockState(pos.offset(direction)); +@@ -111,18 +112,18 @@ + { + if (iblockstate.isBlockNormalCube()) + { +- return BlockRedstoneWire.EnumAttachPosition.UP; ++ return EnumAttachPosition.UP; + } + +- return BlockRedstoneWire.EnumAttachPosition.SIDE; ++ return EnumAttachPosition.SIDE; + } + } + +- return BlockRedstoneWire.EnumAttachPosition.NONE; ++ return EnumAttachPosition.NONE; + } + else + { +- return BlockRedstoneWire.EnumAttachPosition.SIDE; ++ return EnumAttachPosition.SIDE; + } + } + +@@ -220,6 +221,12 @@ + j = k; + } + ++ if (i != j) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(pos1.getX(), pos1.getY(), pos1.getZ()), i, j); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ j = event.getNewCurrent(); ++ } ++ + if (i != j) + { + state = state.withProperty(POWER, Integer.valueOf(j)); +@@ -319,7 +326,7 @@ + } + } + +- private int getMaxCurrentStrength(World worldIn, BlockPos pos, int strength) ++ public int getMaxCurrentStrength(World worldIn, BlockPos pos, int strength) + { + if (worldIn.getBlockState(pos).getBlock() != this) + { diff --git a/patches/net/minecraft/block/BlockReed.java.patch b/patches/net/minecraft/block/BlockReed.java.patch new file mode 100644 index 00000000..6bd01c9e --- /dev/null +++ b/patches/net/minecraft/block/BlockReed.java.patch @@ -0,0 +1,16 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockReed.java ++++ ../src-work/minecraft/net/minecraft/block/BlockReed.java +@@ -57,9 +57,11 @@ + + if(net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, pos, state, true)) + { +- if (j == 15) ++ if (j >= (byte)Block.range(3.0f, 100.0f / worldIn.spigotConfig.caneModifier * 15.0f + 0.5f, 15.0f)) // Spigot + { +- worldIn.setBlockState(pos.up(), this.getDefaultState()); ++// worldIn.setBlockState(pos.up(), this.getDefaultState()); ++ BlockPos upPos = pos.up(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(worldIn, upPos.getX(), upPos.getY(), upPos.getZ(), this, 0); + worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(0)), 4); + } + else diff --git a/patches/net/minecraft/block/BlockSapling.java.patch b/patches/net/minecraft/block/BlockSapling.java.patch new file mode 100644 index 00000000..87ee9ca7 --- /dev/null +++ b/patches/net/minecraft/block/BlockSapling.java.patch @@ -0,0 +1,132 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockSapling.java ++++ ../src-work/minecraft/net/minecraft/block/BlockSapling.java +@@ -1,5 +1,6 @@ + package net.minecraft.block; + ++import java.util.List; + import java.util.Random; + import net.minecraft.block.properties.IProperty; + import net.minecraft.block.properties.PropertyEnum; +@@ -24,12 +25,17 @@ + import net.minecraft.world.gen.feature.WorldGenTaiga2; + import net.minecraft.world.gen.feature.WorldGenTrees; + import net.minecraft.world.gen.feature.WorldGenerator; ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.event.world.StructureGrowEvent; + + public class BlockSapling extends BlockBush implements IGrowable + { + public static final PropertyEnum TYPE = PropertyEnum.create("type", BlockPlanks.EnumType.class); + public static final PropertyInteger STAGE = PropertyInteger.create("stage", 0, 1); + protected static final AxisAlignedBB SAPLING_AABB = new AxisAlignedBB(0.09999999403953552D, 0.0D, 0.09999999403953552D, 0.8999999761581421D, 0.800000011920929D, 0.8999999761581421D); ++ public static TreeType treeType; // CraftBukkit + + protected BlockSapling() + { +@@ -54,9 +60,31 @@ + super.updateTick(worldIn, pos, state, rand); + + if (!worldIn.isAreaLoaded(pos, 1)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light +- if (worldIn.getLightFromNeighbors(pos.up()) >= 9 && rand.nextInt(7) == 0) +- { ++ if (worldIn.isLightLevel(pos.up(), 9) && rand.nextInt(Math.max(2, (int)(100.0f / worldIn.spigotConfig.saplingModifier * 7.0f + 0.5f))) == 0) { ++ worldIn.captureTreeGeneration = true; + this.grow(worldIn, pos, state, rand); ++ worldIn.captureTreeGeneration = false; ++ if (worldIn.capturedBlockSnapshots.size() > 0) { ++ TreeType treeType = BlockSapling.treeType; ++ BlockSapling.treeType = null; ++ Location location = new Location(worldIn.getWorld(), pos.getX(), pos.getY(), pos.getZ()); ++ List blocks = (List) worldIn.capturedBlockSnapshots.clone(); ++ List blockstates = new java.util.ArrayList(); ++ for (net.minecraftforge.common.util.BlockSnapshot snapshot : blocks) { ++ blockstates.add(new org.bukkit.craftbukkit.block.CraftBlockState(snapshot)); ++ } ++ worldIn.capturedBlockSnapshots.clear(); ++ StructureGrowEvent event = null; ++ if (treeType != null) { ++ event = new StructureGrowEvent(location, treeType, false, null, blockstates); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event == null || !event.isCancelled()) { ++ for (BlockState blockstate : blockstates) { ++ blockstate.update(true); ++ } ++ } ++ } + } + } + } +@@ -76,7 +104,17 @@ + public void generateTree(World worldIn, BlockPos pos, IBlockState state, Random rand) + { + if (!net.minecraftforge.event.terraingen.TerrainGen.saplingGrowTree(worldIn, rand, pos)) return; +- WorldGenerator worldgenerator = (WorldGenerator)(rand.nextInt(10) == 0 ? new WorldGenBigTree(true) : new WorldGenTrees(true)); ++ // CraftBukkit start - Turn ternary operator into if statement to set treeType ++ // WorldGenerator worldgenerator = (WorldGenerator)(rand.nextInt(10) == 0 ? new WorldGenBigTree(true) : new WorldGenTrees(true)); ++ WorldGenerator worldgenerator; ++ if (rand.nextInt(10) == 0) { ++ treeType = TreeType.BIG_TREE; ++ worldgenerator = new WorldGenBigTree(true); ++ } else { ++ treeType = TreeType.TREE; ++ worldgenerator = new WorldGenTrees(true); ++ } ++ // CraftBukkit end + int i = 0; + int j = 0; + boolean flag = false; +@@ -92,6 +130,7 @@ + { + if (this.isTwoByTwoOfType(worldIn, pos, i, j, BlockPlanks.EnumType.SPRUCE)) + { ++ treeType = TreeType.MEGA_REDWOOD; // CraftBukkit + worldgenerator = new WorldGenMegaPineTree(false, rand.nextBoolean()); + flag = true; + break label68; +@@ -103,11 +142,13 @@ + { + i = 0; + j = 0; ++ treeType = TreeType.REDWOOD; // CraftBukkit + worldgenerator = new WorldGenTaiga2(true); + } + + break; + case BIRCH: ++ treeType = TreeType.BIRCH; // CraftBukkit + worldgenerator = new WorldGenBirchTree(true, false); + break; + case JUNGLE: +@@ -121,6 +162,7 @@ + { + if (this.isTwoByTwoOfType(worldIn, pos, i, j, BlockPlanks.EnumType.JUNGLE)) + { ++ treeType = TreeType.JUNGLE; // CraftBukkit + worldgenerator = new WorldGenMegaJungle(true, 10, 20, iblockstate, iblockstate1); + flag = true; + break label82; +@@ -132,11 +174,13 @@ + { + i = 0; + j = 0; ++ treeType = TreeType.SMALL_JUNGLE; // CraftBukkit + worldgenerator = new WorldGenTrees(true, 4 + rand.nextInt(7), iblockstate, iblockstate1, false); + } + + break; + case ACACIA: ++ treeType = TreeType.ACACIA; // CraftBukkit + worldgenerator = new WorldGenSavannaTree(true); + break; + case DARK_OAK: +@@ -148,6 +192,7 @@ + { + if (this.isTwoByTwoOfType(worldIn, pos, i, j, BlockPlanks.EnumType.DARK_OAK)) + { ++ treeType = TreeType.DARK_OAK; // CraftBukkit + worldgenerator = new WorldGenCanopyTree(true); + flag = true; + break label96; diff --git a/patches/net/minecraft/block/BlockShulkerBox.java.patch b/patches/net/minecraft/block/BlockShulkerBox.java.patch new file mode 100644 index 00000000..52e23de6 --- /dev/null +++ b/patches/net/minecraft/block/BlockShulkerBox.java.patch @@ -0,0 +1,30 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockShulkerBox.java ++++ ../src-work/minecraft/net/minecraft/block/BlockShulkerBox.java +@@ -44,7 +44,7 @@ + public class BlockShulkerBox extends BlockContainer + { + public static final PropertyEnum FACING = PropertyDirection.create("facing"); +- private final EnumDyeColor color; ++ public final EnumDyeColor color; + + public BlockShulkerBox(EnumDyeColor colorIn) + { +@@ -162,7 +162,7 @@ + + public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune) + { +- } ++ } + + public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) + { +@@ -201,8 +201,8 @@ + + spawnAsEntity(worldIn, pos, itemstack); + } +- + worldIn.updateComparatorOutputLevel(pos, state.getBlock()); ++ tileentityshulkerbox.clear(); // Paper - This was intended to be called in Vanilla (is checked in the if statement above if has been called) - Fixes dupe issues + } + + super.breakBlock(worldIn, pos, state); diff --git a/patches/net/minecraft/block/BlockSilverfish.java.patch b/patches/net/minecraft/block/BlockSilverfish.java.patch new file mode 100644 index 00000000..252fb218 --- /dev/null +++ b/patches/net/minecraft/block/BlockSilverfish.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockSilverfish.java ++++ ../src-work/minecraft/net/minecraft/block/BlockSilverfish.java +@@ -14,6 +14,7 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.event.entity.CreatureSpawnEvent; + + public class BlockSilverfish extends Block + { +@@ -63,7 +64,7 @@ + { + EntitySilverfish entitysilverfish = new EntitySilverfish(worldIn); + entitysilverfish.setLocationAndAngles((double)pos.getX() + 0.5D, (double)pos.getY(), (double)pos.getZ() + 0.5D, 0.0F, 0.0F); +- worldIn.spawnEntity(entitysilverfish); ++ worldIn.spawnEntity(entitysilverfish, CreatureSpawnEvent.SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason + entitysilverfish.spawnExplosionParticle(); + } + } diff --git a/patches/net/minecraft/block/BlockSkull.java.patch b/patches/net/minecraft/block/BlockSkull.java.patch new file mode 100644 index 00000000..ded3ed70 --- /dev/null +++ b/patches/net/minecraft/block/BlockSkull.java.patch @@ -0,0 +1,138 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockSkull.java ++++ ../src-work/minecraft/net/minecraft/block/BlockSkull.java +@@ -40,6 +40,8 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.util.BlockStateListPopulator; ++import org.bukkit.event.entity.CreatureSpawnEvent; + + public class BlockSkull extends BlockContainer + { +@@ -128,6 +130,30 @@ + return new ItemStack(Items.SKULL, 1, i); + } + ++ // CraftBukkit start - Special case dropping so we can get info from the tile entity ++ @Override ++ public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune) { ++ if (worldIn.rand.nextFloat() < chance) { ++ TileEntity tileentity = worldIn.getTileEntity(pos); ++ ++ if (tileentity instanceof TileEntitySkull) { ++ TileEntitySkull tileentityskull = (TileEntitySkull) tileentity; ++ ItemStack itemstack = this.getItem(worldIn, pos, state); ++ ++ if (tileentityskull.getSkullType() == 3 && tileentityskull.getPlayerProfile() != null) { ++ itemstack.setTagCompound(new NBTTagCompound()); ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ NBTUtil.writeGameProfile(nbttagcompound, tileentityskull.getPlayerProfile()); ++ itemstack.getTagCompound().setTag("SkullOwner", nbttagcompound); ++ } ++ ++ spawnAsEntity(worldIn, pos, itemstack); ++ } ++ } ++ } ++ // CraftBukkit end ++ + public void onBlockHarvested(World worldIn, BlockPos pos, IBlockState state, EntityPlayer player) + { + if (player.capabilities.isCreativeMode) +@@ -135,7 +161,7 @@ + state = state.withProperty(NODROP, Boolean.valueOf(true)); + worldIn.setBlockState(pos, state, 4); + } +- this.dropBlockAsItem(worldIn, pos, state, 0); ++ // this.dropBlockAsItem(worldIn, pos, state, 0); Kettle - prevents doubled and creative mode drops + + super.onBlockHarvested(worldIn, pos, state, player); + } +@@ -147,7 +173,9 @@ + public void getDrops(net.minecraft.util.NonNullList drops, IBlockAccess worldIn, BlockPos pos, IBlockState state, int fortune) + { + { +- if (!((Boolean)state.getValue(NODROP)).booleanValue()) ++ // CraftBukkit start - Drop item in code above, not here ++ // if (!((Boolean)state.getValue(NODROP)).booleanValue()) ++ if (false) + { + TileEntity tileentity = worldIn.getTileEntity(pos); + +@@ -189,6 +217,7 @@ + + public void checkWitherSpawn(World worldIn, BlockPos pos, TileEntitySkull te) + { ++ if (worldIn.captureBlockSnapshots) return; + if (te.getSkullType() == 1 && pos.getY() >= 2 && worldIn.getDifficulty() != EnumDifficulty.PEACEFUL && !worldIn.isRemote) + { + BlockPattern blockpattern = this.getWitherPattern(); +@@ -196,10 +225,14 @@ + + if (blockpattern$patternhelper != null) + { ++ BlockStateListPopulator blockList = new BlockStateListPopulator(worldIn.getWorld()); + for (int i = 0; i < 3; ++i) + { + BlockWorldState blockworldstate = blockpattern$patternhelper.translateOffset(i, 0, 0); +- worldIn.setBlockState(blockworldstate.getPos(), blockworldstate.getBlockState().withProperty(NODROP, Boolean.valueOf(true)), 2); ++ BlockPos pos1 = blockworldstate.getPos(); ++ IBlockState data = blockworldstate.getBlockState().withProperty(BlockSkull.NODROP, true); ++ blockList.setTypeAndData(pos1.getX(), pos1.getY(), pos1.getZ(), data.getBlock(), data.getBlock().getMetaFromState(data), 2); ++// worldIn.setBlockState(blockworldstate.getPos(), blockworldstate.getBlockState().withProperty(NODROP, Boolean.valueOf(true)), 2); + } + + for (int j = 0; j < blockpattern.getPalmLength(); ++j) +@@ -207,7 +240,10 @@ + for (int k = 0; k < blockpattern.getThumbLength(); ++k) + { + BlockWorldState blockworldstate1 = blockpattern$patternhelper.translateOffset(j, k, 0); +- worldIn.setBlockState(blockworldstate1.getPos(), Blocks.AIR.getDefaultState(), 2); ++ BlockPos pos1 = blockworldstate1.getPos(); ++ blockList.setTypeAndData(pos1.getX(), pos1.getY(), pos1.getZ(), Blocks.AIR, 0, 2); ++// worldIn.setBlockState(blockworldstate1.getPos(), Blocks.AIR.getDefaultState(), 2); ++ + } + } + +@@ -217,25 +253,24 @@ + entitywither.setLocationAndAngles((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.55D, (double)blockpos1.getZ() + 0.5D, blockpattern$patternhelper.getForwards().getAxis() == EnumFacing.Axis.X ? 0.0F : 90.0F, 0.0F); + entitywither.renderYawOffset = blockpattern$patternhelper.getForwards().getAxis() == EnumFacing.Axis.X ? 0.0F : 90.0F; + entitywither.ignite(); ++ if (worldIn.spawnEntity(entitywither, CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) { ++ blockList.updateList(); + +- for (EntityPlayerMP entityplayermp : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entitywither.getEntityBoundingBox().grow(50.0D))) +- { +- CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp, entitywither); +- } ++ for (EntityPlayerMP entityplayermp : worldIn.getEntitiesWithinAABB(EntityPlayerMP.class, entitywither.getEntityBoundingBox().grow(50.0D))) { ++ CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayermp, entitywither); ++ } + +- worldIn.spawnEntity(entitywither); ++// worldIn.spawnEntity(entitywither); + +- for (int l = 0; l < 120; ++l) +- { +- worldIn.spawnParticle(EnumParticleTypes.SNOWBALL, (double)blockpos.getX() + worldIn.rand.nextDouble(), (double)(blockpos.getY() - 2) + worldIn.rand.nextDouble() * 3.9D, (double)blockpos.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); +- } ++ for (int l = 0; l < 120; ++l) { ++ worldIn.spawnParticle(EnumParticleTypes.SNOWBALL, (double) blockpos.getX() + worldIn.rand.nextDouble(), (double) (blockpos.getY() - 2) + worldIn.rand.nextDouble() * 3.9D, (double) blockpos.getZ() + worldIn.rand.nextDouble(), 0.0D, 0.0D, 0.0D); ++ } + +- for (int i1 = 0; i1 < blockpattern.getPalmLength(); ++i1) +- { +- for (int j1 = 0; j1 < blockpattern.getThumbLength(); ++j1) +- { +- BlockWorldState blockworldstate2 = blockpattern$patternhelper.translateOffset(i1, j1, 0); +- worldIn.notifyNeighborsRespectDebug(blockworldstate2.getPos(), Blocks.AIR, false); ++ for (int i1 = 0; i1 < blockpattern.getPalmLength(); ++i1) { ++ for (int j1 = 0; j1 < blockpattern.getThumbLength(); ++j1) { ++ BlockWorldState blockworldstate2 = blockpattern$patternhelper.translateOffset(i1, j1, 0); ++ worldIn.notifyNeighborsRespectDebug(blockworldstate2.getPos(), Blocks.AIR, false); ++ } + } + } + } diff --git a/patches/net/minecraft/block/BlockSnow.java.patch b/patches/net/minecraft/block/BlockSnow.java.patch new file mode 100644 index 00000000..8854bc69 --- /dev/null +++ b/patches/net/minecraft/block/BlockSnow.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockSnow.java ++++ ../src-work/minecraft/net/minecraft/block/BlockSnow.java +@@ -131,6 +131,9 @@ + { + if (worldIn.getLightFor(EnumSkyBlock.BLOCK, pos) > 11) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), Blocks.AIR).isCancelled()) { ++ return; ++ } + worldIn.setBlockToAir(pos); + } + } diff --git a/patches/net/minecraft/block/BlockSnowBlock.java.patch b/patches/net/minecraft/block/BlockSnowBlock.java.patch new file mode 100644 index 00000000..58be1bbb --- /dev/null +++ b/patches/net/minecraft/block/BlockSnowBlock.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockSnowBlock.java ++++ ../src-work/minecraft/net/minecraft/block/BlockSnowBlock.java +@@ -4,6 +4,7 @@ + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.creativetab.CreativeTabs; ++import net.minecraft.init.Blocks; + import net.minecraft.init.Items; + import net.minecraft.item.Item; + import net.minecraft.util.math.BlockPos; +@@ -33,6 +34,9 @@ + { + if (worldIn.getLightFor(EnumSkyBlock.BLOCK, pos) > 11) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), Blocks.AIR).isCancelled()) { ++ return; ++ } + this.dropBlockAsItem(worldIn, pos, worldIn.getBlockState(pos), 0); + worldIn.setBlockToAir(pos); + } diff --git a/patches/net/minecraft/block/BlockStaticLiquid.java.patch b/patches/net/minecraft/block/BlockStaticLiquid.java.patch new file mode 100644 index 00000000..0176c84b --- /dev/null +++ b/patches/net/minecraft/block/BlockStaticLiquid.java.patch @@ -0,0 +1,37 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockStaticLiquid.java ++++ ../src-work/minecraft/net/minecraft/block/BlockStaticLiquid.java +@@ -7,6 +7,7 @@ + import net.minecraft.util.EnumFacing; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockStaticLiquid extends BlockLiquid + { +@@ -63,6 +64,11 @@ + { + if (this.isSurroundingBlockFlammable(worldIn, blockpos)) + { ++ if (worldIn.getBlockState(blockpos) != Blocks.FIRE) { ++ if (CraftEventFactory.callBlockIgniteEvent(worldIn, blockpos.getX(), blockpos.getY(), blockpos.getZ(), pos.getX(), pos.getY(), pos.getZ()).isCancelled()) { ++ continue; ++ } ++ } + worldIn.setBlockState(blockpos, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, blockpos, pos, Blocks.FIRE.getDefaultState())); + return; + } +@@ -86,6 +92,14 @@ + + if (worldIn.isAirBlock(blockpos1.up()) && this.getCanBlockBurn(worldIn, blockpos1)) + { ++ // CraftBukkit start - Prevent lava putting something on fire ++ BlockPos up = blockpos1.up(); ++ if (worldIn.getBlockState(up) != Blocks.FIRE) { ++ if (CraftEventFactory.callBlockIgniteEvent(worldIn, up.getX(), up.getY(), up.getZ(), pos.getX(), pos.getY(), pos.getZ()).isCancelled()) { ++ continue; ++ } ++ } ++ // CraftBukkit end + worldIn.setBlockState(blockpos1.up(), net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(worldIn, blockpos1.up(), pos, Blocks.FIRE.getDefaultState())); + } + } diff --git a/patches/net/minecraft/block/BlockStem.java.patch b/patches/net/minecraft/block/BlockStem.java.patch new file mode 100644 index 00000000..9ceef8b8 --- /dev/null +++ b/patches/net/minecraft/block/BlockStem.java.patch @@ -0,0 +1,49 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockStem.java ++++ ../src-work/minecraft/net/minecraft/block/BlockStem.java +@@ -19,6 +19,7 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockStem extends BlockBush implements IGrowable + { +@@ -67,7 +68,7 @@ + super.updateTick(worldIn, pos, state, rand); + + if (!worldIn.isAreaLoaded(pos, 1)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light +- if (worldIn.getLightFromNeighbors(pos.up()) >= 9) ++ if (worldIn.isLightLevel(pos.up(), 9)) // Paper + { + float f = BlockCrops.getGrowthChance(this, worldIn, pos); + +@@ -78,7 +79,8 @@ + if (i < 7) + { + IBlockState newState = state.withProperty(AGE, Integer.valueOf(i + 1)); +- worldIn.setBlockState(pos, newState, 2); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, getMetaFromState(newState)); ++// worldIn.setBlockState(pos, newState, 2); + } + else + { +@@ -96,7 +98,8 @@ + + if (worldIn.isAirBlock(pos) && (block.canSustainPlant(soil, worldIn, pos.down(), EnumFacing.UP, this) || block == Blocks.DIRT || block == Blocks.GRASS)) + { +- worldIn.setBlockState(pos, this.crop.getDefaultState()); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this.crop, 0); ++// worldIn.setBlockState(pos, this.crop.getDefaultState()); + } + } + net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos)); +@@ -107,7 +110,8 @@ + public void growStem(World worldIn, BlockPos pos, IBlockState state) + { + int i = ((Integer)state.getValue(AGE)).intValue() + MathHelper.getInt(worldIn.rand, 2, 5); +- worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(Math.min(7, i))), 2); ++ CraftEventFactory.handleBlockGrowEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), this, Math.min(7, i)); ++// worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(Math.min(7, i))), 2); + } + + public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune) diff --git a/patches/net/minecraft/block/BlockTNT.java.patch b/patches/net/minecraft/block/BlockTNT.java.patch new file mode 100644 index 00000000..78a0b751 --- /dev/null +++ b/patches/net/minecraft/block/BlockTNT.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockTNT.java ++++ ../src-work/minecraft/net/minecraft/block/BlockTNT.java +@@ -115,6 +115,9 @@ + + if (entityarrow.isBurning()) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityarrow, pos, Blocks.AIR, 0).isCancelled()) { ++ return; ++ } + this.explode(worldIn, pos, worldIn.getBlockState(pos).withProperty(EXPLODE, Boolean.valueOf(true)), entityarrow.shootingEntity instanceof EntityLivingBase ? (EntityLivingBase)entityarrow.shootingEntity : null); + worldIn.setBlockToAir(pos); + } diff --git a/patches/net/minecraft/block/BlockTrapDoor.java.patch b/patches/net/minecraft/block/BlockTrapDoor.java.patch new file mode 100644 index 00000000..2c36476f --- /dev/null +++ b/patches/net/minecraft/block/BlockTrapDoor.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockTrapDoor.java ++++ ../src-work/minecraft/net/minecraft/block/BlockTrapDoor.java +@@ -24,6 +24,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockTrapDoor extends Block + { +@@ -130,6 +131,18 @@ + + if (flag || blockIn.getDefaultState().canProvidePower()) + { ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.block.Block bblock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ int power = bblock.getBlockPower(); ++ int oldPower = state.getValue(OPEN) ? 15 : 0; ++ ++ if (oldPower == 0 ^ power == 0 || blockIn.getDefaultState().hasComparatorInputOverride()) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bblock, oldPower, power); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ flag = eventRedstone.getNewCurrent() > 0; ++ } ++ + boolean flag1 = ((Boolean)state.getValue(OPEN)).booleanValue(); + + if (flag1 != flag) diff --git a/patches/net/minecraft/block/BlockTripWire.java.patch b/patches/net/minecraft/block/BlockTripWire.java.patch new file mode 100644 index 00000000..aea16c54 --- /dev/null +++ b/patches/net/minecraft/block/BlockTripWire.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockTripWire.java ++++ ../src-work/minecraft/net/minecraft/block/BlockTripWire.java +@@ -25,6 +25,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityInteractEvent; + + public class BlockTripWire extends Block + { +@@ -181,6 +182,40 @@ + } + } + ++ // CraftBukkit start - Call interact even when triggering connected tripwire ++ if (flag != flag1 && flag1 && iblockstate.getValue(ATTACHED)) { ++ org.bukkit.World bworld = worldIn.getWorld(); ++ org.bukkit.plugin.PluginManager manager = worldIn.getServer().getPluginManager(); ++ org.bukkit.block.Block block = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ boolean allowed = false; ++ ++ // If all of the events are cancelled block the tripwire trigger, else allow ++ for (Object object : list) { ++ if (object != null) { ++ org.bukkit.event.Cancellable cancellable; ++ ++ if (object instanceof EntityPlayer) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((EntityPlayer) object, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else if (object instanceof Entity) { ++ cancellable = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block); ++ manager.callEvent((EntityInteractEvent) cancellable); ++ } else { ++ continue; ++ } ++ ++ if (!cancellable.isCancelled()) { ++ allowed = true; ++ break; ++ } ++ } ++ } ++ ++ if (!allowed) { ++ return; ++ } ++ } ++ // CraftBukkit end ++ + if (flag1 != flag) + { + iblockstate = iblockstate.withProperty(POWERED, Boolean.valueOf(flag1)); diff --git a/patches/net/minecraft/block/BlockTripWireHook.java.patch b/patches/net/minecraft/block/BlockTripWireHook.java.patch new file mode 100644 index 00000000..a87e5089 --- /dev/null +++ b/patches/net/minecraft/block/BlockTripWireHook.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockTripWireHook.java ++++ ../src-work/minecraft/net/minecraft/block/BlockTripWireHook.java +@@ -27,6 +27,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.block.BlockRedstoneEvent; + + public class BlockTripWireHook extends Block + { +@@ -197,6 +198,15 @@ + this.playSound(worldIn, blockpos1, flag2, flag3, flag, flag1); + } + ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, 15, 0); ++ worldIn.getServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() > 0) { ++ return; ++ } ++ + this.playSound(worldIn, pos, flag2, flag3, flag, flag1); + + if (!p_176260_4_) diff --git a/patches/net/minecraft/block/BlockVine.java.patch b/patches/net/minecraft/block/BlockVine.java.patch new file mode 100644 index 00000000..9bee4334 --- /dev/null +++ b/patches/net/minecraft/block/BlockVine.java.patch @@ -0,0 +1,79 @@ +--- ../src-base/minecraft/net/minecraft/block/BlockVine.java ++++ ../src-work/minecraft/net/minecraft/block/BlockVine.java +@@ -27,6 +27,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class BlockVine extends Block implements net.minecraftforge.common.IShearable + { +@@ -185,7 +186,7 @@ + { + if (!worldIn.isRemote) + { +- if (worldIn.rand.nextInt(4) == 0 && worldIn.isAreaLoaded(pos, 4)) // Forge: check area to prevent loading unloaded chunks ++ if (worldIn.rand.nextInt(Math.max(1, (int)(100.0f / worldIn.spigotConfig.vineModifier) * 4)) == 0 && worldIn.isAreaLoaded(pos, 4)) // Forge: check area to prevent loading unloaded chunks // Spigot + { + int i = 4; + int j = 5; +@@ -233,7 +234,11 @@ + + if (((Boolean)iblockstate2.getValue(NORTH)).booleanValue() || ((Boolean)iblockstate2.getValue(EAST)).booleanValue() || ((Boolean)iblockstate2.getValue(SOUTH)).booleanValue() || ((Boolean)iblockstate2.getValue(WEST)).booleanValue()) + { +- worldIn.setBlockState(blockpos2, iblockstate2, 2); ++// worldIn.setBlockState(blockpos2, iblockstate2, 2); ++ BlockPos target = blockpos2; ++ org.bukkit.block.Block source = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(target.getX(), target.getY(), target.getZ()); ++ CraftEventFactory.handleBlockSpreadEvent(block, source, this, getMetaFromState(iblockstate2)); + } + } + else if (enumfacing1.getAxis().isHorizontal() && !((Boolean)state.getValue(getPropertyFor(enumfacing1))).booleanValue()) +@@ -253,21 +258,30 @@ + BlockPos blockpos = blockpos4.offset(enumfacing3); + BlockPos blockpos1 = blockpos4.offset(enumfacing4); + ++ org.bukkit.block.Block source = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block bukkitBlock = worldIn.getWorld().getBlockAt(blockpos4.getX(), blockpos4.getY(), blockpos4.getZ()); ++ + if (flag1 && this.canAttachTo(worldIn, blockpos.offset(enumfacing3), enumfacing3)) + { +- worldIn.setBlockState(blockpos4, this.getDefaultState().withProperty(getPropertyFor(enumfacing3), Boolean.valueOf(true)), 2); ++// worldIn.setBlockState(blockpos4, this.getDefaultState().withProperty(getPropertyFor(enumfacing3), Boolean.valueOf(true)), 2); ++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, getMetaFromState(this.getDefaultState().withProperty(getPropertyFor(enumfacing3), true))); + } + else if (flag2 && this.canAttachTo(worldIn, blockpos1.offset(enumfacing4), enumfacing4)) + { +- worldIn.setBlockState(blockpos4, this.getDefaultState().withProperty(getPropertyFor(enumfacing4), Boolean.valueOf(true)), 2); ++// worldIn.setBlockState(blockpos4, this.getDefaultState().withProperty(getPropertyFor(enumfacing4), Boolean.valueOf(true)), 2); ++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, getMetaFromState(this.getDefaultState().withProperty(getPropertyFor(enumfacing4), true))); + } + else if (flag1 && worldIn.isAirBlock(blockpos) && this.canAttachTo(worldIn, blockpos, enumfacing1)) + { +- worldIn.setBlockState(blockpos, this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), Boolean.valueOf(true)), 2); ++// worldIn.setBlockState(blockpos, this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), Boolean.valueOf(true)), 2); ++ bukkitBlock = worldIn.getWorld().getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()); ++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, getMetaFromState(this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), true))); + } + else if (flag2 && worldIn.isAirBlock(blockpos1) && this.canAttachTo(worldIn, blockpos1, enumfacing1)) + { +- worldIn.setBlockState(blockpos1, this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), Boolean.valueOf(true)), 2); ++// worldIn.setBlockState(blockpos1, this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), Boolean.valueOf(true)), 2); ++ bukkitBlock = worldIn.getWorld().getBlockAt(blockpos1.getX(), blockpos1.getY(), blockpos1.getZ()); ++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, getMetaFromState(this.getDefaultState().withProperty(getPropertyFor(enumfacing1.getOpposite()), true))); + } + } + else if (iblockstate3.getBlockFaceShape(worldIn, blockpos4, enumfacing1) == BlockFaceShape.SOLID) +@@ -298,7 +312,10 @@ + + if (((Boolean)iblockstate1.getValue(NORTH)).booleanValue() || ((Boolean)iblockstate1.getValue(EAST)).booleanValue() || ((Boolean)iblockstate1.getValue(SOUTH)).booleanValue() || ((Boolean)iblockstate1.getValue(WEST)).booleanValue()) + { +- worldIn.setBlockState(blockpos3, iblockstate1, 2); ++// worldIn.setBlockState(blockpos3, iblockstate1, 2); ++ org.bukkit.block.Block source = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block bukkitBlock = worldIn.getWorld().getBlockAt(blockpos3.getX(), blockpos3.getY(), blockpos3.getZ()); ++ CraftEventFactory.handleBlockSpreadEvent(bukkitBlock, source, this, getMetaFromState(iblockstate1)); + } + } + else if (block == this) diff --git a/patches/net/minecraft/client/gui/GuiIngame.java.patch b/patches/net/minecraft/client/gui/GuiIngame.java.patch new file mode 100644 index 00000000..620abd72 --- /dev/null +++ b/patches/net/minecraft/client/gui/GuiIngame.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/client/gui/GuiIngame.java ++++ ../src-work/minecraft/net/minecraft/client/gui/GuiIngame.java +@@ -499,7 +499,7 @@ + // FORGE - Move status icon check down from above so renderHUDEffect will still be called without a status icon + if (potion.hasStatusIcon()) + this.drawTexturedModalRect(k + 3, l + 3, i1 % 8 * 18, 198 + i1 / 8 * 18, 18, 18); +- potion.renderHUDEffect(potioneffect, this, k, l, this.zLevel, f); ++ potion.renderHUDEffect(k, l, potioneffect, mc, f); + } + } + } diff --git a/patches/net/minecraft/client/gui/inventory/GuiContainerCreative.java.patch b/patches/net/minecraft/client/gui/inventory/GuiContainerCreative.java.patch new file mode 100644 index 00000000..2a1c20be --- /dev/null +++ b/patches/net/minecraft/client/gui/inventory/GuiContainerCreative.java.patch @@ -0,0 +1,22 @@ +--- ../src-base/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.java ++++ ../src-work/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.java +@@ -40,6 +40,7 @@ + import net.minecraft.util.text.TextFormatting; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.inventory.InventoryView; + import org.lwjgl.input.Keyboard; + import org.lwjgl.input.Mouse; + +@@ -1042,6 +1043,11 @@ + return this.itemList.size() > 45; + } + ++ @Override ++ public InventoryView getBukkitView() { ++ return null; ++ } ++ + public ItemStack transferStackInSlot(EntityPlayer playerIn, int index) + { + if (index >= this.inventorySlots.size() - 9 && index < this.inventorySlots.size()) diff --git a/patches/net/minecraft/client/multiplayer/WorldClient.java.patch b/patches/net/minecraft/client/multiplayer/WorldClient.java.patch new file mode 100644 index 00000000..5abd6904 --- /dev/null +++ b/patches/net/minecraft/client/multiplayer/WorldClient.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/client/multiplayer/WorldClient.java ++++ ../src-work/minecraft/net/minecraft/client/multiplayer/WorldClient.java +@@ -115,7 +115,7 @@ + return this.clientChunkProvider; + } + +- protected boolean isChunkLoaded(int x, int z, boolean allowEmpty) ++ public boolean isChunkLoaded(int x, int z, boolean allowEmpty) + { + return allowEmpty || !this.getChunkProvider().provideChunk(x, z).isEmpty(); + } diff --git a/patches/net/minecraft/client/renderer/InventoryEffectRenderer.java.patch b/patches/net/minecraft/client/renderer/InventoryEffectRenderer.java.patch new file mode 100644 index 00000000..dea71d5d --- /dev/null +++ b/patches/net/minecraft/client/renderer/InventoryEffectRenderer.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/InventoryEffectRenderer.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/InventoryEffectRenderer.java +@@ -88,7 +88,7 @@ + this.drawTexturedModalRect(i + 6, j + 7, 0 + i1 % 8 * 18, 198 + i1 / 8 * 18, 18, 18); + } + +- potion.renderInventoryEffect(potioneffect, this, i, j, this.zLevel); ++ potion.renderInventoryEffect(i, j, potioneffect, mc); + if (!potion.shouldRenderInvText(potioneffect)) { j += l; continue; } + String s1 = I18n.format(potion.getName()); + diff --git a/patches/net/minecraft/command/CommandDebug.java.patch b/patches/net/minecraft/command/CommandDebug.java.patch new file mode 100644 index 00000000..815946d4 --- /dev/null +++ b/patches/net/minecraft/command/CommandDebug.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandDebug.java ++++ ../src-work/minecraft/net/minecraft/command/CommandDebug.java +@@ -13,6 +13,7 @@ + import net.minecraft.profiler.Profiler; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.math.BlockPos; ++import net.minecraft.util.text.TextComponentString; + import org.apache.commons.io.IOUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -40,6 +41,14 @@ + + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { ++ // CraftBukkit start - only allow use when enabled (so that no blank profile results occur) ++ if (!server.profiler.ENABLED) { ++ sender.sendMessage(new TextComponentString("Vanilla debug profiling is disabled.")); ++ sender.sendMessage(new TextComponentString("To enable, restart the server with `-DenableDebugMethodProfiler=true' before `-jar'.")); ++ sender.sendMessage(new TextComponentString("Use `/timings' for plugin timings.")); ++ return; ++ } ++ // CraftBukkit end + if (args.length < 1) + { + throw new WrongUsageException("commands.debug.usage", new Object[0]); diff --git a/patches/net/minecraft/command/CommandExecuteAt.java.patch b/patches/net/minecraft/command/CommandExecuteAt.java.patch new file mode 100644 index 00000000..e1ac958c --- /dev/null +++ b/patches/net/minecraft/command/CommandExecuteAt.java.patch @@ -0,0 +1,34 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandExecuteAt.java ++++ ../src-work/minecraft/net/minecraft/command/CommandExecuteAt.java +@@ -7,9 +7,11 @@ + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.Entity; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.tileentity.CommandBlockBaseLogic; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.Vec3d; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.command.ProxiedNativeCommandSender; + + public class CommandExecuteAt extends CommandBase + { +@@ -78,7 +80,8 @@ + + try + { +- int j = icommandmanager.executeCommand(icommandsender, s); ++ org.bukkit.command.CommandSender bukkitSender = CommandBlockBaseLogic.unwrapSender(sender); ++ int j = CommandBlockBaseLogic.executeCommand(icommandsender, new ProxiedNativeCommandSender(icommandsender, bukkitSender, entity.getBukkitEntity()), s); + + if (j < 1) + { +@@ -87,6 +90,9 @@ + } + catch (Throwable var23) + { ++ if (var23 instanceof CommandException) { ++ throw (CommandException) var23; ++ } + throw new CommandException("commands.execute.failed", new Object[] {s, entity.getName()}); + } + } diff --git a/patches/net/minecraft/command/CommandGameMode.java.patch b/patches/net/minecraft/command/CommandGameMode.java.patch new file mode 100644 index 00000000..044a187c --- /dev/null +++ b/patches/net/minecraft/command/CommandGameMode.java.patch @@ -0,0 +1,27 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandGameMode.java ++++ ../src-work/minecraft/net/minecraft/command/CommandGameMode.java +@@ -4,9 +4,11 @@ + import java.util.List; + import javax.annotation.Nullable; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.text.ITextComponent; ++import net.minecraft.util.text.TextComponentString; + import net.minecraft.util.text.TextComponentTranslation; + import net.minecraft.world.GameType; + import net.minecraft.world.WorldSettings; +@@ -39,6 +41,12 @@ + GameType gametype = this.getGameModeFromCommand(sender, args[0]); + EntityPlayer entityplayer = args.length >= 2 ? getPlayer(server, sender, args[1]) : getCommandSenderAsPlayer(sender); + entityplayer.setGameType(gametype); ++ // CraftBukkit start - handle event cancelling the change ++ if (((EntityPlayerMP) entityplayer).interactionManager.getGameType() != gametype) { ++ sender.sendMessage(new TextComponentString("Failed to set the gamemode of '" + entityplayer.getName() + "'")); ++ return; ++ } ++ // CraftBukkit end + ITextComponent itextcomponent = new TextComponentTranslation("gameMode." + gametype.getName(), new Object[0]); + + if (sender.getEntityWorld().getGameRules().getBoolean("sendCommandFeedback")) diff --git a/patches/net/minecraft/command/CommandGameRule.java.patch b/patches/net/minecraft/command/CommandGameRule.java.patch new file mode 100644 index 00000000..6245e46b --- /dev/null +++ b/patches/net/minecraft/command/CommandGameRule.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandGameRule.java ++++ ../src-work/minecraft/net/minecraft/command/CommandGameRule.java +@@ -29,7 +29,7 @@ + + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { +- GameRules gamerules = this.getOverWorldGameRules(server); ++ GameRules gamerules = sender.getEntityWorld().getGameRules(); // CraftBukkit - Use current world + String s = args.length > 0 ? args[0] : ""; + String s1 = args.length > 1 ? buildString(args, 1) : ""; + diff --git a/patches/net/minecraft/command/CommandHandler.java.patch b/patches/net/minecraft/command/CommandHandler.java.patch new file mode 100644 index 00000000..c993cb64 --- /dev/null +++ b/patches/net/minecraft/command/CommandHandler.java.patch @@ -0,0 +1,94 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandHandler.java ++++ ../src-work/minecraft/net/minecraft/command/CommandHandler.java +@@ -3,12 +3,6 @@ + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +-import java.util.Collections; +-import java.util.List; +-import java.util.Map; +-import java.util.Set; +-import java.util.Map.Entry; +-import javax.annotation.Nullable; + import net.minecraft.entity.Entity; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.math.BlockPos; +@@ -16,7 +10,16 @@ + import net.minecraft.util.text.TextFormatting; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.command.CraftSimpleCommandMap; ++import org.bukkit.craftbukkit.command.ModCustomCommand; + ++import javax.annotation.Nullable; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Set; ++ + public abstract class CommandHandler implements ICommandManager + { + private static final Logger LOGGER = LogManager.getLogger(); +@@ -27,10 +30,10 @@ + { + rawCommand = rawCommand.trim(); + +- if (rawCommand.startsWith("/")) +- { +- rawCommand = rawCommand.substring(1); +- } ++// if (rawCommand.startsWith("/")) ++// { ++// rawCommand = rawCommand.substring(1); ++// } + + String[] astring = rawCommand.split(" "); + String s = astring[0]; +@@ -144,9 +147,8 @@ + + protected abstract MinecraftServer getServer(); + +- public ICommand registerCommand(ICommand command) +- { +- this.commandMap.put(command.getName(), command); ++ public ICommand registerCommand(ICommand command) { ++ /*this.commandMap.put(command.getName(), command); + this.commandSet.add(command); + + for (String s : command.getAliases()) +@@ -159,6 +161,34 @@ + } + } + ++ return command;*/ ++ // register commands with permission nodes, defaulting to class name ++ return registerCommand(command, command.getClass().getName()); ++ } ++ ++ private ICommand registerCommand(String permissionGroup, ICommand command) { ++ return registerCommand(command, permissionGroup + "." + command.getName()); ++ } ++ ++ public ICommand registerCommand(ICommand command, String permissionNode) { ++ this.commandMap.put(command.getName(), command); ++ this.commandSet.add(command); ++ // register vanilla commands with Bukkit to support permissions. ++ CraftSimpleCommandMap commandMap = MinecraftServer.getServerCB().server.getCraftCommandMap(); ++ ModCustomCommand customCommand = new ModCustomCommand(command.getName()); ++ customCommand.setPermission(permissionNode); ++ List list = command.getAliases(); ++ if (list != null) customCommand.setAliases(list); ++ commandMap.register(command.getName(), customCommand); ++ LogManager.getLogger().info("Registered command " + command.getName() + " with permission node " + permissionNode); ++ if (list != null) { ++ for (String s : list) { ++ ICommand icommand = (ICommand)this.commandMap.get(s); ++ if (icommand == null || !icommand.getName().equals(s)) { ++ this.commandMap.put(s, command); ++ } ++ } ++ } + return command; + } + diff --git a/patches/net/minecraft/command/CommandSenderWrapper.java.patch b/patches/net/minecraft/command/CommandSenderWrapper.java.patch new file mode 100644 index 00000000..c93a7e65 --- /dev/null +++ b/patches/net/minecraft/command/CommandSenderWrapper.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandSenderWrapper.java ++++ ../src-work/minecraft/net/minecraft/command/CommandSenderWrapper.java +@@ -11,7 +11,7 @@ + + public class CommandSenderWrapper implements ICommandSender + { +- private final ICommandSender delegate; ++ public final ICommandSender delegate; + @Nullable + private final Vec3d positionVector; + @Nullable diff --git a/patches/net/minecraft/command/CommandSpreadPlayers.java.patch b/patches/net/minecraft/command/CommandSpreadPlayers.java.patch new file mode 100644 index 00000000..86a7896f --- /dev/null +++ b/patches/net/minecraft/command/CommandSpreadPlayers.java.patch @@ -0,0 +1,49 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandSpreadPlayers.java ++++ ../src-work/minecraft/net/minecraft/command/CommandSpreadPlayers.java +@@ -10,6 +10,7 @@ + import java.util.Set; + import javax.annotation.Nullable; + import net.minecraft.block.material.Material; ++import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.Entity; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.scoreboard.Team; +@@ -18,6 +19,7 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.util.text.TextComponentTranslation; + import net.minecraft.world.World; ++import net.minecraft.world.gen.ChunkProviderServer; + + public class CommandSpreadPlayers extends CommandBase + { +@@ -356,7 +358,7 @@ + { + blockpos = blockpos.down(); + +- if (worldIn.getBlockState(blockpos).getMaterial() != Material.AIR) ++ if (getBlockState(worldIn, blockpos).getMaterial() != Material.AIR) + { + return blockpos.getY() + 1; + } +@@ -372,7 +374,7 @@ + while (blockpos.getY() > 0) + { + blockpos = blockpos.down(); +- Material material = worldIn.getBlockState(blockpos).getMaterial(); ++ Material material = getBlockState(worldIn, blockpos).getMaterial(); + + if (material != Material.AIR) + { +@@ -388,5 +390,12 @@ + this.x = MathHelper.nextDouble(rand, p_111097_2_, p_111097_6_); + this.z = MathHelper.nextDouble(rand, p_111097_4_, p_111097_8_); + } ++ ++ // CraftBukkit start - add a version of getBlockState which force loads chunks ++ private static IBlockState getBlockState(World world, BlockPos position) { ++ ((ChunkProviderServer) world.getChunkProvider()).loadChunk(position.getX() >> 4, position.getZ() >> 4); ++ return world.getBlockState(position); ++ } ++ // CraftBukkit end + } + } diff --git a/patches/net/minecraft/command/CommandTP.java.patch b/patches/net/minecraft/command/CommandTP.java.patch new file mode 100644 index 00000000..4faa0ab2 --- /dev/null +++ b/patches/net/minecraft/command/CommandTP.java.patch @@ -0,0 +1,42 @@ +--- ../src-base/minecraft/net/minecraft/command/CommandTP.java ++++ ../src-work/minecraft/net/minecraft/command/CommandTP.java +@@ -74,25 +74,12 @@ + { + Entity entity1 = getEntity(server, sender, args[args.length - 1]); + +- if (entity1.world != entity.world) +- { +- throw new CommandException("commands.tp.notSameDimension", new Object[0]); ++ // CraftBukkit Start ++ // Use Bukkit teleport method in all cases. It has cross dimensional handling, events ++ if (entity.getBukkitEntity().teleport(entity.getBukkitEntity(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND)) { ++ notifyCommandListener(sender, (ICommand) this, "commands.tp.success", new Object[] { entity.getName(), entity.getName()}); ++ // CraftBukkit End + } +- else +- { +- entity.dismountRidingEntity(); +- +- if (entity instanceof EntityPlayerMP) +- { +- ((EntityPlayerMP)entity).connection.setPlayerLocation(entity1.posX, entity1.posY, entity1.posZ, entity1.rotationYaw, entity1.rotationPitch); +- } +- else +- { +- entity.setLocationAndAngles(entity1.posX, entity1.posY, entity1.posZ, entity1.rotationYaw, entity1.rotationPitch); +- } +- +- notifyCommandListener(sender, this, "commands.tp.success", new Object[] {entity.getName(), entity1.getName()}); +- } + } + } + } +@@ -143,7 +130,7 @@ + } + + teleportingEntity.dismountRidingEntity(); +- ((EntityPlayerMP)teleportingEntity).connection.setPlayerLocation(argX.getAmount(), argY.getAmount(), argZ.getAmount(), f, f1, set); ++ ((EntityPlayerMP)teleportingEntity).connection.setPlayerLocation(argX.getAmount(), argY.getAmount(), argZ.getAmount(), f, f1, set, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); + teleportingEntity.setRotationYawHead(f); + } + else diff --git a/patches/net/minecraft/command/FunctionObject.java.patch b/patches/net/minecraft/command/FunctionObject.java.patch new file mode 100644 index 00000000..fd0e6fa6 --- /dev/null +++ b/patches/net/minecraft/command/FunctionObject.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/command/FunctionObject.java ++++ ../src-work/minecraft/net/minecraft/command/FunctionObject.java +@@ -5,6 +5,7 @@ + import java.util.List; + import javax.annotation.Nullable; + import net.minecraft.advancements.FunctionManager; ++import net.minecraft.tileentity.CommandBlockBaseLogic; + import net.minecraft.util.ResourceLocation; + + public class FunctionObject +@@ -108,7 +109,14 @@ + + public void execute(FunctionManager functionManagerIn, ICommandSender sender, ArrayDeque commandQueue, int maxCommandChainLength) + { +- functionManagerIn.getCommandManager().executeCommand(sender, this.command); ++// functionManagerIn.getCommandManager().executeCommand(sender, this.command); ++ org.bukkit.command.CommandSender bukkitSender; ++ if (sender instanceof FunctionManager.CustomFunctionListener) { ++ bukkitSender = ((FunctionManager.CustomFunctionListener) sender).sender; ++ } else { ++ bukkitSender = CommandBlockBaseLogic.unwrapSender(sender); ++ } ++ CommandBlockBaseLogic.executeSafely(sender, bukkitSender, this.command); + } + + public String toString() diff --git a/patches/net/minecraft/command/ICommandManager.java.patch b/patches/net/minecraft/command/ICommandManager.java.patch new file mode 100644 index 00000000..8a25c1ba --- /dev/null +++ b/patches/net/minecraft/command/ICommandManager.java.patch @@ -0,0 +1,9 @@ +--- ../src-base/minecraft/net/minecraft/command/ICommandManager.java ++++ ../src-work/minecraft/net/minecraft/command/ICommandManager.java +@@ -14,4 +14,6 @@ + List getPossibleCommands(ICommandSender sender); + + Map getCommands(); ++ ++ + } diff --git a/patches/net/minecraft/command/ServerCommandManager.java.patch b/patches/net/minecraft/command/ServerCommandManager.java.patch new file mode 100644 index 00000000..e7a5f747 --- /dev/null +++ b/patches/net/minecraft/command/ServerCommandManager.java.patch @@ -0,0 +1,32 @@ +--- ../src-base/minecraft/net/minecraft/command/ServerCommandManager.java ++++ ../src-work/minecraft/net/minecraft/command/ServerCommandManager.java +@@ -40,6 +40,10 @@ + public ServerCommandManager(MinecraftServer serverIn) + { + this.server = serverIn; ++ CommandBase.setCommandListener(this); ++ } ++ ++ public void registerVanillaCommands() { + this.registerCommand(new CommandTime()); + this.registerCommand(new CommandGameMode()); + this.registerCommand(new CommandDifficulty()); +@@ -89,8 +93,7 @@ + this.registerCommand(new CommandLocate()); + this.registerCommand(new CommandReload()); + this.registerCommand(new CommandFunction()); +- +- if (serverIn.isDedicatedServer()) ++ if (server.isDedicatedServer()) + { + this.registerCommand(new CommandOp()); + this.registerCommand(new CommandDeOp()); +@@ -147,7 +150,7 @@ + } + } + +- if (sender != minecraftserver && minecraftserver.worlds[0].getGameRules().getBoolean("logAdminCommands")) ++ if (sender != minecraftserver && minecraftserver.worlds[0].getGameRules().getBoolean("logAdminCommands") && !org.spigotmc.SpigotConfig.silentCommandBlocks) // Spigot + { + minecraftserver.sendMessage(itextcomponent); + } diff --git a/patches/net/minecraft/command/server/CommandTeleport.java.patch b/patches/net/minecraft/command/server/CommandTeleport.java.patch new file mode 100644 index 00000000..79fc2e0b --- /dev/null +++ b/patches/net/minecraft/command/server/CommandTeleport.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/command/server/CommandTeleport.java ++++ ../src-work/minecraft/net/minecraft/command/server/CommandTeleport.java +@@ -91,7 +91,7 @@ + } + + teleportingEntity.dismountRidingEntity(); +- ((EntityPlayerMP)teleportingEntity).connection.setPlayerLocation(argX.getResult(), argY.getResult(), argZ.getResult(), f, f1, set); ++ ((EntityPlayerMP)teleportingEntity).connection.setPlayerLocation(argX.getResult(), argY.getResult(), argZ.getResult(), f, f1, set, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); + teleportingEntity.setRotationYawHead(f); + } + else diff --git a/patches/net/minecraft/dispenser/BehaviorDefaultDispenseItem.java.patch b/patches/net/minecraft/dispenser/BehaviorDefaultDispenseItem.java.patch new file mode 100644 index 00000000..1c45a202 --- /dev/null +++ b/patches/net/minecraft/dispenser/BehaviorDefaultDispenseItem.java.patch @@ -0,0 +1,88 @@ +--- ../src-base/minecraft/net/minecraft/dispenser/BehaviorDefaultDispenseItem.java ++++ ../src-work/minecraft/net/minecraft/dispenser/BehaviorDefaultDispenseItem.java +@@ -5,6 +5,8 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.util.EnumFacing; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; + + public class BehaviorDefaultDispenseItem implements IBehaviorDispenseItem + { +@@ -21,7 +23,11 @@ + EnumFacing enumfacing = (EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING); + IPosition iposition = BlockDispenser.getDispensePosition(source); + ItemStack itemstack = stack.splitStack(1); +- doDispense(source.getWorld(), itemstack, 6, enumfacing, iposition); ++ // CraftBukkit start ++ if (!doDispense(source.getWorld(), itemstack, 6, enumfacing, source)) { ++ itemstack.grow(1); ++ } ++ // CraftBukkit end + return stack; + } + +@@ -51,6 +57,63 @@ + worldIn.spawnEntity(entityitem); + } + ++ // CraftBukkit start - void -> boolean return, IPosition -> ISourceBlock last argument ++ public static boolean doDispense(World worldIn, ItemStack stack, int speed, EnumFacing facing, IBlockSource source) ++ { ++ IPosition position = BlockDispenser.getDispensePosition(source); ++ double d0 = position.getX(); ++ double d1 = position.getY(); ++ double d2 = position.getZ(); ++ ++ if (facing.getAxis() == EnumFacing.Axis.Y) ++ { ++ d1 = d1 - 0.125D; ++ } ++ else ++ { ++ d1 = d1 - 0.15625D; ++ } ++ ++ EntityItem entityitem = new EntityItem(worldIn, d0, d1, d2, stack); ++ double d3 = worldIn.rand.nextDouble() * 0.1D + 0.2D; ++ entityitem.motionX = (double)facing.getFrontOffsetX() * d3; ++ entityitem.motionY = 0.20000000298023224D; ++ entityitem.motionZ = (double)facing.getFrontOffsetZ() * d3; ++ entityitem.motionX += worldIn.rand.nextGaussian() * 0.007499999832361937D * (double)speed; ++ entityitem.motionY += worldIn.rand.nextGaussian() * 0.007499999832361937D * (double)speed; ++ entityitem.motionZ += worldIn.rand.nextGaussian() * 0.007499999832361937D * (double)speed; ++ ++ org.bukkit.block.Block block = worldIn.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(entityitem.motionX, entityitem.motionY, entityitem.motionZ)); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ entityitem.setItem(CraftItemStack.asNMSCopy(event.getItem())); ++ entityitem.motionX = event.getVelocity().getX(); ++ entityitem.motionY = event.getVelocity().getY(); ++ entityitem.motionZ = event.getVelocity().getZ(); ++ ++ if (!event.getItem().getType().equals(craftItem.getType())) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior.getClass() != BehaviorDefaultDispenseItem.class) { ++ idispensebehavior.dispense(source, eventStack); ++ } else { ++ worldIn.spawnEntity(entityitem); ++ } ++ return false; ++ } ++ worldIn.spawnEntity(entityitem); ++ return true; ++ } ++ // CraftBukkit end ++ + protected void playDispenseSound(IBlockSource source) + { + source.getWorld().playEvent(1000, source.getBlockPos(), 0); diff --git a/patches/net/minecraft/dispenser/BehaviorProjectileDispense.java.patch b/patches/net/minecraft/dispenser/BehaviorProjectileDispense.java.patch new file mode 100644 index 00000000..3b598c33 --- /dev/null +++ b/patches/net/minecraft/dispenser/BehaviorProjectileDispense.java.patch @@ -0,0 +1,50 @@ +--- ../src-base/minecraft/net/minecraft/dispenser/BehaviorProjectileDispense.java ++++ ../src-work/minecraft/net/minecraft/dispenser/BehaviorProjectileDispense.java +@@ -6,6 +6,8 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.util.EnumFacing; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; + + public abstract class BehaviorProjectileDispense extends BehaviorDefaultDispenseItem + { +@@ -15,9 +17,36 @@ + IPosition iposition = BlockDispenser.getDispensePosition(source); + EnumFacing enumfacing = (EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING); + IProjectile iprojectile = this.getProjectileEntity(world, iposition, stack); +- iprojectile.shoot((double)enumfacing.getFrontOffsetX(), (double)((float)enumfacing.getFrontOffsetY() + 0.1F), (double)enumfacing.getFrontOffsetZ(), this.getProjectileVelocity(), this.getProjectileInaccuracy()); ++// iprojectile.shoot((double)enumfacing.getFrontOffsetX(), (double)((float)enumfacing.getFrontOffsetY() + 0.1F), (double)enumfacing.getFrontOffsetZ(), this.getProjectileVelocity(), this.getProjectileInaccuracy()); ++ // CraftBukkit start ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumfacing.getFrontOffsetX(), (double) ((float) enumfacing.getFrontOffsetY() + 0.1F), (double) enumfacing.getFrontOffsetZ())); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ iprojectile.shoot(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.getProjectileVelocity(), this.getProjectileInaccuracy()); ++ ((Entity) iprojectile).projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(source.getBlockTileEntity()); ++ // CraftBukkit end + world.spawnEntity((Entity)iprojectile); +- stack.shrink(1); ++// stack.shrink(1); // CraftBukkit - Handled during event processing + return stack; + } + diff --git a/patches/net/minecraft/enchantment/Enchantment.java.patch b/patches/net/minecraft/enchantment/Enchantment.java.patch new file mode 100644 index 00000000..564efa1c --- /dev/null +++ b/patches/net/minecraft/enchantment/Enchantment.java.patch @@ -0,0 +1,69 @@ +--- ../src-base/minecraft/net/minecraft/enchantment/Enchantment.java ++++ ../src-work/minecraft/net/minecraft/enchantment/Enchantment.java +@@ -178,36 +178,36 @@ + public static void registerEnchantments() + { + EntityEquipmentSlot[] aentityequipmentslot = new EntityEquipmentSlot[] {EntityEquipmentSlot.HEAD, EntityEquipmentSlot.CHEST, EntityEquipmentSlot.LEGS, EntityEquipmentSlot.FEET}; +- REGISTRY.register(0, new ResourceLocation("protection"), new EnchantmentProtection(Enchantment.Rarity.COMMON, EnchantmentProtection.Type.ALL, aentityequipmentslot)); +- REGISTRY.register(1, new ResourceLocation("fire_protection"), new EnchantmentProtection(Enchantment.Rarity.UNCOMMON, EnchantmentProtection.Type.FIRE, aentityequipmentslot)); +- REGISTRY.register(2, new ResourceLocation("feather_falling"), new EnchantmentProtection(Enchantment.Rarity.UNCOMMON, EnchantmentProtection.Type.FALL, aentityequipmentslot)); +- REGISTRY.register(3, new ResourceLocation("blast_protection"), new EnchantmentProtection(Enchantment.Rarity.RARE, EnchantmentProtection.Type.EXPLOSION, aentityequipmentslot)); +- REGISTRY.register(4, new ResourceLocation("projectile_protection"), new EnchantmentProtection(Enchantment.Rarity.UNCOMMON, EnchantmentProtection.Type.PROJECTILE, aentityequipmentslot)); +- REGISTRY.register(5, new ResourceLocation("respiration"), new EnchantmentOxygen(Enchantment.Rarity.RARE, aentityequipmentslot)); +- REGISTRY.register(6, new ResourceLocation("aqua_affinity"), new EnchantmentWaterWorker(Enchantment.Rarity.RARE, aentityequipmentslot)); +- REGISTRY.register(7, new ResourceLocation("thorns"), new EnchantmentThorns(Enchantment.Rarity.VERY_RARE, aentityequipmentslot)); +- REGISTRY.register(8, new ResourceLocation("depth_strider"), new EnchantmentWaterWalker(Enchantment.Rarity.RARE, aentityequipmentslot)); +- REGISTRY.register(9, new ResourceLocation("frost_walker"), new EnchantmentFrostWalker(Enchantment.Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.FEET})); +- REGISTRY.register(10, new ResourceLocation("binding_curse"), new EnchantmentBindingCurse(Enchantment.Rarity.VERY_RARE, aentityequipmentslot)); +- REGISTRY.register(16, new ResourceLocation("sharpness"), new EnchantmentDamage(Enchantment.Rarity.COMMON, 0, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(17, new ResourceLocation("smite"), new EnchantmentDamage(Enchantment.Rarity.UNCOMMON, 1, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(18, new ResourceLocation("bane_of_arthropods"), new EnchantmentDamage(Enchantment.Rarity.UNCOMMON, 2, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(19, new ResourceLocation("knockback"), new EnchantmentKnockback(Enchantment.Rarity.UNCOMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(20, new ResourceLocation("fire_aspect"), new EnchantmentFireAspect(Enchantment.Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(21, new ResourceLocation("looting"), new EnchantmentLootBonus(Enchantment.Rarity.RARE, EnumEnchantmentType.WEAPON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(22, new ResourceLocation("sweeping"), new EnchantmentSweepingEdge(Enchantment.Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(32, new ResourceLocation("efficiency"), new EnchantmentDigging(Enchantment.Rarity.COMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(33, new ResourceLocation("silk_touch"), new EnchantmentUntouching(Enchantment.Rarity.VERY_RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(34, new ResourceLocation("unbreaking"), new EnchantmentDurability(Enchantment.Rarity.UNCOMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(35, new ResourceLocation("fortune"), new EnchantmentLootBonus(Enchantment.Rarity.RARE, EnumEnchantmentType.DIGGER, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(48, new ResourceLocation("power"), new EnchantmentArrowDamage(Enchantment.Rarity.COMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(49, new ResourceLocation("punch"), new EnchantmentArrowKnockback(Enchantment.Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(50, new ResourceLocation("flame"), new EnchantmentArrowFire(Enchantment.Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(51, new ResourceLocation("infinity"), new EnchantmentArrowInfinite(Enchantment.Rarity.VERY_RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(61, new ResourceLocation("luck_of_the_sea"), new EnchantmentLootBonus(Enchantment.Rarity.RARE, EnumEnchantmentType.FISHING_ROD, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(62, new ResourceLocation("lure"), new EnchantmentFishingSpeed(Enchantment.Rarity.RARE, EnumEnchantmentType.FISHING_ROD, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); +- REGISTRY.register(70, new ResourceLocation("mending"), new EnchantmentMending(Enchantment.Rarity.RARE, EntityEquipmentSlot.values())); +- REGISTRY.register(71, new ResourceLocation("vanishing_curse"), new EnchantmentVanishingCurse(Enchantment.Rarity.VERY_RARE, EntityEquipmentSlot.values())); ++ REGISTRY.register(0, new ResourceLocation("protection"), new EnchantmentProtection(Rarity.COMMON, EnchantmentProtection.Type.ALL, aentityequipmentslot)); ++ REGISTRY.register(1, new ResourceLocation("fire_protection"), new EnchantmentProtection(Rarity.UNCOMMON, EnchantmentProtection.Type.FIRE, aentityequipmentslot)); ++ REGISTRY.register(2, new ResourceLocation("feather_falling"), new EnchantmentProtection(Rarity.UNCOMMON, EnchantmentProtection.Type.FALL, aentityequipmentslot)); ++ REGISTRY.register(3, new ResourceLocation("blast_protection"), new EnchantmentProtection(Rarity.RARE, EnchantmentProtection.Type.EXPLOSION, aentityequipmentslot)); ++ REGISTRY.register(4, new ResourceLocation("projectile_protection"), new EnchantmentProtection(Rarity.UNCOMMON, EnchantmentProtection.Type.PROJECTILE, aentityequipmentslot)); ++ REGISTRY.register(5, new ResourceLocation("respiration"), new EnchantmentOxygen(Rarity.RARE, aentityequipmentslot)); ++ REGISTRY.register(6, new ResourceLocation("aqua_affinity"), new EnchantmentWaterWorker(Rarity.RARE, aentityequipmentslot)); ++ REGISTRY.register(7, new ResourceLocation("thorns"), new EnchantmentThorns(Rarity.VERY_RARE, aentityequipmentslot)); ++ REGISTRY.register(8, new ResourceLocation("depth_strider"), new EnchantmentWaterWalker(Rarity.RARE, aentityequipmentslot)); ++ REGISTRY.register(9, new ResourceLocation("frost_walker"), new EnchantmentFrostWalker(Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.FEET})); ++ REGISTRY.register(10, new ResourceLocation("binding_curse"), new EnchantmentBindingCurse(Rarity.VERY_RARE, aentityequipmentslot)); ++ REGISTRY.register(16, new ResourceLocation("sharpness"), new EnchantmentDamage(Rarity.COMMON, 0, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(17, new ResourceLocation("smite"), new EnchantmentDamage(Rarity.UNCOMMON, 1, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(18, new ResourceLocation("bane_of_arthropods"), new EnchantmentDamage(Rarity.UNCOMMON, 2, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(19, new ResourceLocation("knockback"), new EnchantmentKnockback(Rarity.UNCOMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(20, new ResourceLocation("fire_aspect"), new EnchantmentFireAspect(Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(21, new ResourceLocation("looting"), new EnchantmentLootBonus(Rarity.RARE, EnumEnchantmentType.WEAPON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(22, new ResourceLocation("sweeping"), new EnchantmentSweepingEdge(Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(32, new ResourceLocation("efficiency"), new EnchantmentDigging(Rarity.COMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(33, new ResourceLocation("silk_touch"), new EnchantmentUntouching(Rarity.VERY_RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(34, new ResourceLocation("unbreaking"), new EnchantmentDurability(Rarity.UNCOMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(35, new ResourceLocation("fortune"), new EnchantmentLootBonus(Rarity.RARE, EnumEnchantmentType.DIGGER, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(48, new ResourceLocation("power"), new EnchantmentArrowDamage(Rarity.COMMON, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(49, new ResourceLocation("punch"), new EnchantmentArrowKnockback(Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(50, new ResourceLocation("flame"), new EnchantmentArrowFire(Rarity.RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(51, new ResourceLocation("infinity"), new EnchantmentArrowInfinite(Rarity.VERY_RARE, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(61, new ResourceLocation("luck_of_the_sea"), new EnchantmentLootBonus(Rarity.RARE, EnumEnchantmentType.FISHING_ROD, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(62, new ResourceLocation("lure"), new EnchantmentFishingSpeed(Rarity.RARE, EnumEnchantmentType.FISHING_ROD, new EntityEquipmentSlot[] {EntityEquipmentSlot.MAINHAND})); ++ REGISTRY.register(70, new ResourceLocation("mending"), new EnchantmentMending(Rarity.RARE, EntityEquipmentSlot.values())); ++ REGISTRY.register(71, new ResourceLocation("vanishing_curse"), new EnchantmentVanishingCurse(Rarity.VERY_RARE, EntityEquipmentSlot.values())); + } + + public static enum Rarity diff --git a/patches/net/minecraft/enchantment/EnchantmentFrostWalker.java.patch b/patches/net/minecraft/enchantment/EnchantmentFrostWalker.java.patch new file mode 100644 index 00000000..908964c9 --- /dev/null +++ b/patches/net/minecraft/enchantment/EnchantmentFrostWalker.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/enchantment/EnchantmentFrostWalker.java ++++ ../src-work/minecraft/net/minecraft/enchantment/EnchantmentFrostWalker.java +@@ -59,10 +59,15 @@ + { + IBlockState iblockstate1 = worldIn.getBlockState(blockpos$mutableblockpos1); + +- if (iblockstate1.getMaterial() == Material.WATER && (iblockstate1.getBlock() == net.minecraft.init.Blocks.WATER || iblockstate1.getBlock() == net.minecraft.init.Blocks.FLOWING_WATER) && ((Integer)iblockstate1.getValue(BlockLiquid.LEVEL)).intValue() == 0 && worldIn.mayPlace(Blocks.FROSTED_ICE, blockpos$mutableblockpos1, false, EnumFacing.DOWN, (Entity)null)) ++ if (iblockstate1.getMaterial() == Material.WATER && (iblockstate1.getBlock() == Blocks.WATER || iblockstate1.getBlock() == Blocks.FLOWING_WATER) && ((Integer)iblockstate1.getValue(BlockLiquid.LEVEL)).intValue() == 0 && worldIn.mayPlace(Blocks.FROSTED_ICE, blockpos$mutableblockpos1, false, EnumFacing.DOWN, (Entity)null)) + { +- worldIn.setBlockState(blockpos$mutableblockpos1, Blocks.FROSTED_ICE.getDefaultState()); +- worldIn.scheduleUpdate(blockpos$mutableblockpos1.toImmutable(), Blocks.FROSTED_ICE, MathHelper.getInt(living.getRNG(), 60, 120)); ++ // CraftBukkit Start - Call EntityBlockFormEvent for Frost Walker ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldIn, blockpos$mutableblockpos1, Blocks.FROSTED_ICE.getDefaultState(), living)) { ++ worldIn.scheduleUpdate(blockpos$mutableblockpos1.toImmutable(), Blocks.FROSTED_ICE, MathHelper.getInt(living.getRNG(), 60, 120)); ++ } ++ // CraftBukkit End ++// worldIn.setBlockState(blockpos$mutableblockpos1, Blocks.FROSTED_ICE.getDefaultState()); ++// worldIn.scheduleUpdate(blockpos$mutableblockpos1.toImmutable(), Blocks.FROSTED_ICE, MathHelper.getInt(living.getRNG(), 60, 120)); + } + } + } diff --git a/patches/net/minecraft/entity/Entity.java.patch b/patches/net/minecraft/entity/Entity.java.patch new file mode 100644 index 00000000..2d3cd55e --- /dev/null +++ b/patches/net/minecraft/entity/Entity.java.patch @@ -0,0 +1,1023 @@ +--- ../src-base/minecraft/net/minecraft/entity/Entity.java ++++ ../src-work/minecraft/net/minecraft/entity/Entity.java +@@ -15,7 +15,6 @@ + import net.minecraft.block.Block; + import net.minecraft.block.BlockFence; + import net.minecraft.block.BlockFenceGate; +-import net.minecraft.block.BlockLiquid; + import net.minecraft.block.BlockWall; + import net.minecraft.block.SoundType; + import net.minecraft.block.material.EnumPushReaction; +@@ -32,6 +31,7 @@ + import net.minecraft.entity.effect.EntityLightningBolt; + import net.minecraft.entity.item.EntityBoat; + import net.minecraft.entity.item.EntityItem; ++import net.minecraft.entity.passive.EntityTameable; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; +@@ -77,26 +77,63 @@ + import net.minecraft.util.text.event.HoverEvent; + import net.minecraft.util.text.translation.I18n; + import net.minecraft.world.Explosion; +-import net.minecraft.world.Teleporter; + import net.minecraft.world.World; + import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.Server; ++import org.bukkit.TravelAgent; ++import org.bukkit.block.BlockFace; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.Hanging; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Vehicle; ++import org.spigotmc.CustomTimingsHandler; // Spigot ++import org.bukkit.event.entity.EntityAirChangeEvent; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++import org.bukkit.event.entity.EntityPortalEvent; ++import org.bukkit.event.hanging.HangingBreakByEntityEvent; ++import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; ++import org.bukkit.event.vehicle.VehicleEnterEvent; ++import org.bukkit.event.vehicle.VehicleExitEvent; ++import org.bukkit.plugin.PluginManager; + + public abstract class Entity implements ICommandSender, net.minecraftforge.common.capabilities.ICapabilitySerializable + { ++ // CraftBukkit start ++ private static final int CURRENT_LEVEL = 2; ++ static boolean isLevelAtLeast(NBTTagCompound tag, int level) { ++ return tag.hasKey("Bukkit.updateLevel") && tag.getInteger("Bukkit.updateLevel") >= level; ++ } ++ ++ protected CraftEntity bukkitEntity; ++ EntityTrackerEntry trackedEntity; // Paper ++ public CraftEntity getBukkitEntity() { ++ if (bukkitEntity == null) { ++ bukkitEntity = CraftEntity.getEntity(world.getServer(), this); ++ } ++ return bukkitEntity; ++ } ++ // CraftBukikt end + private static final Logger LOGGER = LogManager.getLogger(); + private static final List EMPTY_EQUIPMENT = Collections.emptyList(); + private static final AxisAlignedBB ZERO_AABB = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D); + private static double renderDistanceWeight = 1.0D; +- private static int nextEntityID; ++ private static int nextEntityID = 1; // Paper - MC-111480 - ID 0 is treated as special for DataWatchers, start 1 + private int entityId; + public boolean preventEntitySpawning; +- private final List riddenByEntities; ++ public final List riddenByEntities; // Spigot + protected int rideCooldown; + private Entity ridingEntity; ++ public void setVehicle(Entity entity) { this.ridingEntity = entity; } // Paper // OBFHELPER + public boolean forceSpawn; + public World world; + public double prevPosX; +@@ -137,8 +174,8 @@ + public float entityCollisionReduction; + protected Random rand; + public int ticksExisted; +- private int fire; +- protected boolean inWater; ++ public int fire; ++ public boolean inWater; + public int hurtResistantTime; + protected boolean firstUpdate; + protected boolean isImmuneToFire; +@@ -172,7 +209,7 @@ + protected UUID entityUniqueID; + protected String cachedUniqueIdString; + private final CommandResultStats cmdResultStats; +- protected boolean glowing; ++ public boolean glowing; + private final Set tags; + private boolean isPositionDirty; + private final double[] pistonDeltas; +@@ -182,6 +219,24 @@ + */ + public boolean updateBlocked; + ++ // CraftBukkit start ++ public boolean valid; ++ public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only ++ public boolean forceExplosionKnockback; // SPIGOT-949 ++ ++ public float getBukkitYaw() { ++ return this.rotationYaw; ++ } ++ // CraftBukkit end ++ ++ // Spigot start ++ public final byte activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); ++ public final boolean defaultActivationState; ++ public long activatedTick = Integer.MIN_VALUE; ++ public boolean fromMobSpawner; ++ public void inactiveTick() { } ++ // Spigot end ++ + public Entity(World worldIn) + { + this.entityId = nextEntityID++; +@@ -205,7 +260,12 @@ + if (worldIn != null) + { + this.dimension = worldIn.provider.getDimension(); ++ // Spigot start ++ this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); ++ } else { ++ this.defaultActivationState = false; + } ++ // Spigot end + + this.dataManager = new EntityDataManager(this); + this.dataManager.register(FLAGS, Byte.valueOf((byte)0)); +@@ -215,15 +275,17 @@ + this.dataManager.register(SILENT, Boolean.valueOf(false)); + this.dataManager.register(NO_GRAVITY, Boolean.valueOf(false)); + this.entityInit(); +- net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityEvent.EntityConstructing(this)); +- capabilities = net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(this); ++ if(!(this instanceof EntityPlayer)) { // Kettle - move to EntityPlayer ++ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityEvent.EntityConstructing(this)); ++ capabilities = net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(this); ++ } + } + + /** Forge: Used to store custom data for each entity. */ + private NBTTagCompound customEntityData; + public boolean captureDrops = false; + public java.util.ArrayList capturedDrops = new java.util.ArrayList(); +- private net.minecraftforge.common.capabilities.CapabilityDispatcher capabilities; ++ public net.minecraftforge.common.capabilities.CapabilityDispatcher capabilities; // Kettle - private -> public + + public int getEntityId() + { +@@ -345,8 +407,35 @@ + } + } + +- protected void setRotation(float yaw, float pitch) ++ public void setRotation(float yaw, float pitch) + { ++ // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0 ++ if (Float.isNaN(yaw)) { ++ yaw = 0; ++ } ++ ++ if (yaw == Float.POSITIVE_INFINITY || yaw == Float.NEGATIVE_INFINITY) { ++ if (this instanceof EntityPlayer) { ++ this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid yaw"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Nope"); ++ } ++ yaw = 0; ++ } ++ ++ // pitch was sometimes set to NaN, so we need to set it back to 0 ++ if (Float.isNaN(pitch)) { ++ pitch = 0; ++ } ++ ++ if (pitch == Float.POSITIVE_INFINITY || pitch == Float.NEGATIVE_INFINITY) { ++ if (this instanceof EntityPlayer) { ++ this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid pitch"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Nope"); ++ } ++ pitch = 0; ++ } ++ // CraftBukkit end ++ + this.rotationYaw = yaw % 360.0F; + this.rotationPitch = pitch % 360.0F; + } +@@ -389,72 +478,40 @@ + this.onEntityUpdate(); + } + +- public void onEntityUpdate() +- { +- this.world.profiler.startSection("entityBaseTick"); +- +- if (this.isRiding() && this.getRidingEntity().isDead) +- { +- this.dismountRidingEntity(); +- } +- +- if (this.rideCooldown > 0) +- { +- --this.rideCooldown; +- } +- +- this.prevDistanceWalkedModified = this.distanceWalkedModified; +- this.prevPosX = this.posX; +- this.prevPosY = this.posY; +- this.prevPosZ = this.posZ; +- this.prevRotationPitch = this.rotationPitch; +- this.prevRotationYaw = this.rotationYaw; +- +- if (!this.world.isRemote && this.world instanceof WorldServer) +- { ++ public void postTick() { ++ // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle ++ if (!this.world.isRemote && this.world instanceof WorldServer) { + this.world.profiler.startSection("portal"); +- +- if (this.inPortal) +- { ++ if (this.inPortal) { + MinecraftServer minecraftserver = this.world.getMinecraftServer(); + +- if (minecraftserver.getAllowNether()) +- { +- if (!this.isRiding()) +- { ++ if (true || minecraftserver.getAllowNether()) { // CraftBukkit ++ if (!this.isRiding()) { + int i = this.getMaxInPortalTime(); + +- if (this.portalCounter++ >= i) +- { ++ if (this.portalCounter++ >= i) { + this.portalCounter = i; + this.timeUntilPortal = this.getPortalCooldown(); +- int j; ++ byte b0; + +- if (this.world.provider.getDimensionType().getId() == -1) +- { +- j = 0; ++ if (this.world.provider.getDimensionType().getId() == -1) { ++ b0 = 0; ++ } else { ++ b0 = -1; + } +- else +- { +- j = -1; +- } + +- this.changeDimension(j); ++ this.changeDimension(b0); + } + } + + this.inPortal = false; + } +- } +- else +- { +- if (this.portalCounter > 0) +- { ++ } else { ++ if (this.portalCounter > 0) { + this.portalCounter -= 4; + } + +- if (this.portalCounter < 0) +- { ++ if (this.portalCounter < 0) { + this.portalCounter = 0; + } + } +@@ -462,7 +519,28 @@ + this.decrementTimeUntilPortal(); + this.world.profiler.endSection(); + } ++ } + ++ public void onEntityUpdate() ++ { ++ this.world.profiler.startSection("entityBaseTick"); ++ ++ if (this.isRiding() && this.getRidingEntity().isDead) ++ { ++ this.dismountRidingEntity(); ++ } ++ ++ if (this.rideCooldown > 0) ++ { ++ --this.rideCooldown; ++ } ++ ++ this.prevDistanceWalkedModified = this.distanceWalkedModified; ++ this.prevPosX = this.posX; ++ this.prevPosY = this.posY; ++ this.prevPosZ = this.posZ; ++ this.prevRotationPitch = this.rotationPitch; ++ this.prevRotationYaw = this.rotationYaw; + this.spawnRunningParticles(); + this.handleWaterMovement(); + +@@ -530,6 +608,26 @@ + if (!this.isImmuneToFire) + { + this.attackEntityFrom(DamageSource.LAVA, 4.0F); ++ // CraftBukkit start - Fallen in lava TODO: this event spams! ++ if (this instanceof EntityLiving) { ++ if (fire <= 0) { ++ // not on fire yet ++ // TODO: shouldn't be sending null for the block ++ org.bukkit.block.Block damager = null; // ((WorldServer) this.l).getWorld().getBlockAt(i, j, k); ++ org.bukkit.entity.Entity damagee = this.getBukkitEntity(); ++ EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15); ++ this.world.getServer().getPluginManager().callEvent(combustEvent); ++ ++ if (!combustEvent.isCancelled()) { ++ this.setFire(combustEvent.getDuration()); ++ } ++ } else { ++ // This will be called every single tick the entity is in lava, so don't throw an event ++ this.setFire(15); ++ } ++ return; ++ } ++ // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls + this.setFire(15); + } + } +@@ -579,6 +677,26 @@ + } + else + { ++ // CraftBukkit start - Don't do anything if we aren't moving ++ // We need to do this regardless of whether or not we are moving thanks to portals ++ double d2 = x; ++ double d3 = y; ++ double d4 = z; ++ ++ try { ++ this.doBlockCollisions(); ++ } catch (Throwable throwable) { ++ CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Checking entity block collision"); ++ CrashReportCategory crashreportsystemdetails = crashreport.makeCategory("Entity being checked for collision"); ++ ++ this.addEntityCrashInfo(crashreportsystemdetails); ++ throw new ReportedException(crashreport); ++ } ++ // Check if we're moving ++ if (x == 0 && y == 0 && z == 0 && this.isBeingRidden() && this.isRiding()) { ++ return; ++ } ++ // CraftBukkit end + if (type == MoverType.PISTON) + { + long i = this.world.getTotalWorldTime(); +@@ -648,10 +766,6 @@ + this.motionZ = 0.0D; + } + +- double d2 = x; +- double d3 = y; +- double d4 = z; +- + if ((type == MoverType.SELF || type == MoverType.PLAYER) && this.onGround && this.isSneaking() && this instanceof EntityPlayer) + { + for (double d5 = 0.05D; x != 0.0D && this.world.getCollisionBoxes(this, this.getEntityBoundingBox().offset(x, (double)(-this.stepHeight), 0.0D)).isEmpty(); d2 = x) +@@ -912,6 +1026,26 @@ + block.onLanded(this.world, this); + } + ++ if (collidedHorizontally && getBukkitEntity() instanceof Vehicle) { ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ org.bukkit.block.Block bl = this.world.getWorld().getBlockAt(MathHelper.floor(this.posX), MathHelper.floor(this.posY), MathHelper.floor(this.posZ)); ++ ++ if (d2 > x) { ++ bl = bl.getRelative(BlockFace.EAST); ++ } else if (d2 < x) { ++ bl = bl.getRelative(BlockFace.WEST); ++ } else if (d4 > z) { ++ bl = bl.getRelative(BlockFace.SOUTH); ++ } else if (d4 < z) { ++ bl = bl.getRelative(BlockFace.NORTH); ++ } ++ ++ if (bl.getType() != org.bukkit.Material.AIR) { ++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); ++ world.getServer().getPluginManager().callEvent(event); ++ } ++ } ++ + if (this.canTriggerWalking() && (!this.onGround || !this.isSneaking() || !(this instanceof EntityPlayer)) && !this.isRiding()) + { + double d15 = this.posX - d10; +@@ -958,7 +1092,8 @@ + this.nextFlap = this.playFlySound(this.distanceWalkedOnStepModified); + } + } +- ++ // Move to the top of the method ++ /* + try + { + this.doBlockCollisions(); +@@ -970,6 +1105,7 @@ + this.addEntityCrashInfo(crashreportcategory); + throw new ReportedException(crashreport); + } ++ */ + + boolean flag1 = this.isWet(); + +@@ -983,7 +1119,13 @@ + + if (this.fire == 0) + { +- this.setFire(8); ++// this.setFire(8); ++ EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(null, getBukkitEntity(), 8); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setFire(event.getDuration()); ++ } + } + } + } +@@ -1008,7 +1150,7 @@ + this.posX = (axisalignedbb.minX + axisalignedbb.maxX) / 2.0D; + this.posY = axisalignedbb.minY; + this.posZ = (axisalignedbb.minZ + axisalignedbb.maxZ) / 2.0D; +- if (this.isAddedToWorld() && !this.world.isRemote) this.world.updateEntityWithOptionalForce(this, false); // Forge - Process chunk registration after moving. ++ if (this.isAddedToWorld && !this.world.isRemote) this.world.updateEntityWithOptionalForce(this, false); // Forge - Process chunk registration after moving. + } + + protected SoundEvent getSwimSound() +@@ -1041,9 +1183,14 @@ + + try + { +- iblockstate.getBlock().onEntityCollidedWithBlock(this.world, blockpos$pooledmutableblockpos2, iblockstate, this); ++ if (iblockstate != null) { ++ Block block = iblockstate.getBlock(); ++ if (block != null) { ++ block.onEntityCollidedWithBlock(this.world, blockpos$pooledmutableblockpos2, iblockstate, this); ++ } + this.onInsideBlock(iblockstate); + } ++ } + catch (Throwable throwable) + { + CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Colliding entity with block"); +@@ -1154,6 +1301,14 @@ + } + } + ++ protected void dealFireDamage(float amount) ++ { ++ if (!this.isImmuneToFire) ++ { ++ this.attackEntityFrom(DamageSource.IN_FIRE, (float)amount); ++ } ++ } ++ + public final boolean isImmuneToFire() + { + return this.isImmuneToFire; +@@ -1274,10 +1429,10 @@ + IBlockState iblockstate = this.world.getBlockState(blockpos); + + if(!iblockstate.getBlock().addRunningEffects(iblockstate, world, blockpos, this)) +- if (iblockstate.getRenderType() != EnumBlockRenderType.INVISIBLE) +- { +- this.world.spawnParticle(EnumParticleTypes.BLOCK_CRACK, this.posX + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, this.getEntityBoundingBox().minY + 0.1D, this.posZ + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, -this.motionX * 4.0D, 1.5D, -this.motionZ * 4.0D, Block.getStateId(iblockstate)); +- } ++ if (iblockstate.getRenderType() != EnumBlockRenderType.INVISIBLE) ++ { ++ this.world.spawnParticle(EnumParticleTypes.BLOCK_CRACK, this.posX + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, this.getEntityBoundingBox().minY + 0.1D, this.posZ + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, -this.motionX * 4.0D, 1.5D, -this.motionZ * 4.0D, Block.getStateId(iblockstate)); ++ } + } + + public boolean isInsideOfMaterial(Material materialIn) +@@ -1369,6 +1524,11 @@ + + public void setWorld(World worldIn) + { ++ if (world == null) { ++ setDead(); ++ this.world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); ++ return; ++ } + this.world = worldIn; + } + +@@ -1687,6 +1847,16 @@ + { + compound.setTag("Pos", this.newDoubleNBTList(this.posX, this.posY, this.posZ)); + compound.setTag("Motion", this.newDoubleNBTList(this.motionX, this.motionY, this.motionZ)); ++ // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero ++ // TODO: make sure this is the best way to address this. ++ if (Float.isNaN(this.rotationYaw)) { ++ this.rotationYaw = 0; ++ } ++ ++ if (Float.isNaN(this.rotationPitch)) { ++ this.rotationPitch = 0; ++ } ++ // CraftBukkit end + compound.setTag("Rotation", this.newFloatNBTList(this.rotationYaw, this.rotationPitch)); + compound.setFloat("FallDistance", this.fallDistance); + compound.setShort("Fire", (short)this.fire); +@@ -1696,7 +1866,12 @@ + compound.setBoolean("Invulnerable", this.invulnerable); + compound.setInteger("PortalCooldown", this.timeUntilPortal); + compound.setUniqueId("UUID", this.getUniqueID()); +- ++ // CraftBukkit start ++ // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast ++ compound.setLong("WorldUUIDLeast", this.world.getSaveHandler().getUUID().getLeastSignificantBits()); ++ compound.setLong("WorldUUIDMost", this.world.getSaveHandler().getUUID().getMostSignificantBits()); ++ compound.setInteger("Bukkit.updateLevel", CURRENT_LEVEL); ++ // CraftBukkit end + if (this.hasCustomName()) + { + compound.setString("CustomName", this.getCustomNameTag()); +@@ -1784,21 +1959,6 @@ + this.motionY = nbttaglist2.getDoubleAt(1); + this.motionZ = nbttaglist2.getDoubleAt(2); + +- if (Math.abs(this.motionX) > 10.0D) +- { +- this.motionX = 0.0D; +- } +- +- if (Math.abs(this.motionY) > 10.0D) +- { +- this.motionY = 0.0D; +- } +- +- if (Math.abs(this.motionZ) > 10.0D) +- { +- this.motionZ = 0.0D; +- } +- + this.posX = nbttaglist.getDoubleAt(0); + this.posY = nbttaglist.getDoubleAt(1); + this.posZ = nbttaglist.getDoubleAt(2); +@@ -1869,6 +2029,50 @@ + { + this.setPosition(this.posX, this.posY, this.posZ); + } ++ ++ if (this instanceof EntityLiving) { ++ EntityLiving entity = (EntityLiving) this; ++ ++ // Reset the persistence for tamed animals ++ if (entity instanceof EntityTameable && !isLevelAtLeast(compound, 2) && !compound.getBoolean("PersistenceRequired")) { ++ ((EntityLiving) entity).persistenceRequired = !(entity).canDespawn(); ++ } ++ } ++ double limit = getBukkitEntity() instanceof Vehicle ? 100.0D : 10.0D; ++ if (Math.abs(this.motionX) > limit) { ++ this.motionX = 0.0D; ++ } ++ ++ if (Math.abs(this.motionY) > limit) { ++ this.motionY = 0.0D; ++ } ++ ++ if (Math.abs(this.motionZ) > limit) { ++ this.motionZ = 0.0D; ++ } ++ ++ // Reset world ++ if (this instanceof EntityPlayer) { ++ Server server = Bukkit.getServer(); ++ org.bukkit.World bworld = null; ++ ++ // TODO: Remove World related checks, replaced with WorldUID ++ String worldName = compound.getString("world"); ++ ++ if (compound.hasKey("WorldUUIDMost") && compound.hasKey("WorldUUIDLeast")) { ++ UUID uid = new UUID(compound.getLong("WorldUUIDMost"), compound.getLong("WorldUUIDLeast")); ++ bworld = server.getWorld(uid); ++ } else { ++ bworld = server.getWorld(worldName); ++ } ++ ++ if (bworld == null) { ++ EntityPlayer entityPlayer = (EntityPlayer) this; ++ bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(entityPlayer.dimension).getWorld(); ++ } ++ ++ setWorld(bworld == null? null : ((CraftWorld) bworld).getHandle()); ++ } + } + catch (Throwable throwable) + { +@@ -1885,7 +2089,7 @@ + } + + @Nullable +- protected final String getEntityString() ++ public final String getEntityString() + { + ResourceLocation resourcelocation = EntityList.getKey(this); + return resourcelocation == null ? null : resourcelocation.toString(); +@@ -1945,7 +2149,9 @@ + if (captureDrops) + this.capturedDrops.add(entityitem); + else +- this.world.spawnEntity(entityitem); ++ { ++ this.world.spawnEntity(entityitem); ++ } + return entityitem; + } + } +@@ -2013,7 +2219,7 @@ + this.motionY = 0.0D; + this.motionZ = 0.0D; + if(!updateBlocked) +- this.onUpdate(); ++ this.onUpdate(); + + if (this.isRiding()) + { +@@ -2110,6 +2316,29 @@ + } + else + { ++ com.google.common.base.Preconditions.checkState(!passenger.riddenByEntities.contains(this), "Circular entity riding! %s %s", this, passenger); ++ ++ CraftEntity craft = (CraftEntity) passenger.getBukkitEntity().getVehicle(); ++ Entity orig = craft == null ? null : craft.getHandle(); ++ if (getBukkitEntity() instanceof Vehicle && passenger.getBukkitEntity() instanceof LivingEntity && passenger.world.isChunkLoaded((int) passenger.posX >> 4, (int) passenger.posZ >> 4, false)) { // Boolean not used ++ VehicleEnterEvent event = new VehicleEnterEvent( ++ (Vehicle) getBukkitEntity(), ++ passenger.getBukkitEntity() ++ ); ++ Bukkit.getPluginManager().callEvent(event); ++ CraftEntity craftn = (CraftEntity) passenger.getBukkitEntity().getVehicle(); ++ Entity n = craftn == null ? null : craftn.getHandle(); ++ if (event.isCancelled() || n != orig) { ++ return; ++ } ++ } ++ // Spigot start ++ org.spigotmc.event.entity.EntityMountEvent event = new org.spigotmc.event.entity.EntityMountEvent(passenger.getBukkitEntity(), this.getBukkitEntity()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // Spigot end + if (!this.world.isRemote && passenger instanceof EntityPlayer && !(this.getControllingPassenger() instanceof EntityPlayer)) + { + this.riddenByEntities.add(0, passenger); +@@ -2129,6 +2358,27 @@ + } + else + { ++ passenger.setVehicle(this); // Paper - Set the vehicle back for the event ++ CraftEntity craft = (CraftEntity) passenger.getBukkitEntity().getVehicle(); ++ Entity orig = craft == null ? null : craft.getHandle(); ++ if (getBukkitEntity() instanceof Vehicle && passenger.getBukkitEntity() instanceof LivingEntity) { ++ VehicleExitEvent event = new VehicleExitEvent( ++ (Vehicle) getBukkitEntity(), ++ (LivingEntity) passenger.getBukkitEntity() ++ ); ++ Bukkit.getPluginManager().callEvent(event); ++ CraftEntity craftn = (CraftEntity) passenger.getBukkitEntity().getVehicle(); ++ Entity n = craftn == null ? null : craftn.getHandle(); ++ if (event.isCancelled() || n != orig) { ++ return; ++ } ++ } ++ // Paper start - make EntityDismountEvent cancellable ++ if (!new org.spigotmc.event.entity.EntityDismountEvent(passenger.getBukkitEntity(), this.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ passenger.setVehicle(null); ++ // Paper end + this.riddenByEntities.remove(passenger); + passenger.rideCooldown = 60; + } +@@ -2325,12 +2575,12 @@ + this.setFlag(5, invisible); + } + +- protected boolean getFlag(int flag) ++ public boolean getFlag(int flag) + { + return (((Byte)this.dataManager.get(FLAGS)).byteValue() & 1 << flag) != 0; + } + +- protected void setFlag(int flag, boolean set) ++ public void setFlag(int flag, boolean set) + { + byte b0 = ((Byte)this.dataManager.get(FLAGS)).byteValue(); + +@@ -2351,17 +2601,52 @@ + + public void setAir(int air) + { +- this.dataManager.set(AIR, Integer.valueOf(air)); ++// this.dataManager.set(AIR, Integer.valueOf(air)); ++ EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air); ++ event.getEntity().getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ this.dataManager.set(Entity.AIR, event.getAmount()); + } + +- public void onStruckByLightning(EntityLightningBolt lightningBolt) ++ public void onStruckByLightning(@Nullable EntityLightningBolt lightningBolt) + { ++ if (lightningBolt == null) lightningBolt = new EntityLightningBolt(this.world, this.posX, this.posY, this.posZ, true); ++ final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity(); ++ final org.bukkit.entity.Entity stormBukkitEntity = lightningBolt.getBukkitEntity(); ++ final PluginManager pluginManager = Bukkit.getPluginManager(); ++ ++ if (thisBukkitEntity instanceof Hanging) { ++ HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); ++ pluginManager.callEvent(hangingEvent); ++ ++ if (hangingEvent.isCancelled()) { ++ return; ++ } ++ } ++ ++ if (this.isImmuneToFire) { ++ return; ++ } ++ CraftEventFactory.entityDamage = lightningBolt; ++ if (!this.attackEntityFrom(DamageSource.LIGHTNING_BOLT, 5.0F)) { ++ CraftEventFactory.entityDamage = null; ++ return; ++ } + this.attackEntityFrom(DamageSource.LIGHTNING_BOLT, 5.0F); + ++this.fire; + + if (this.fire == 0) + { +- this.setFire(8); ++ // this.setFire(8); ++ // CraftBukkit start - Call a combust event when lightning strikes ++ EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8); ++ pluginManager.callEvent(entityCombustEvent); ++ if (!entityCombustEvent.isCancelled()) { ++ this.setFire(entityCombustEvent.getDuration()); ++ } ++ // CraftBukkit end + } + } + +@@ -2502,7 +2787,7 @@ + + public String toString() + { +- return String.format("%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName(), this.entityId, this.world == null ? "~NULL~" : this.world.getWorldInfo().getWorldName(), this.posX, this.posY, this.posZ); ++ return String.format("%s[\'%s\'/%d, uuid=\'%s\', l=\'%s\', x=%.2f, y=%.2f, z=%.2f, cx=%d, cd=%d, tl=%d, v=%b, d=%b]", new Object[] { this.getClass().getSimpleName(), this.getName(), Integer.valueOf(this.entityId), this.getUniqueID().toString(), this.world == null ? "~NULL~" : this.world.getWorldInfo().getWorldName(), Double.valueOf(this.posX), Double.valueOf(this.posY), Double.valueOf(this.posZ), chunkCoordX, chunkCoordZ, this.ticksExisted, this.valid, this.isDead}); // Paper - add more information + } + + public boolean isEntityInvulnerable(DamageSource source) +@@ -2540,7 +2825,7 @@ + public Entity changeDimension(int dimensionIn) + { + if (this.world.isRemote || this.isDead) return null; +- return changeDimension(dimensionIn, this.getServer().getWorld(dimensionIn).getDefaultTeleporter()); ++ return changeDimension(dimensionIn, this.getServer().getWorldServer(dimensionIn).getDefaultTeleporter()); + } + + @Nullable // Forge: Entities that require custom handling should override this method, not the other +@@ -2551,53 +2836,67 @@ + if (!net.minecraftforge.common.ForgeHooks.onTravelToDimension(this, dimensionIn)) return null; + this.world.profiler.startSection("changeDimension"); + MinecraftServer minecraftserver = this.getServer(); +- int i = this.dimension; +- WorldServer worldserver = minecraftserver.getWorld(i); +- WorldServer worldserver1 = minecraftserver.getWorld(dimensionIn); +- this.dimension = dimensionIn; +- +- if (i == 1 && dimensionIn == 1 && teleporter.isVanilla()) +- { +- worldserver1 = minecraftserver.getWorld(0); +- this.dimension = 0; ++ // CraftBukkit start - Move logic into new function "teleportTo(Location,boolean)" ++ // int i = this.dimension; ++ // WorldServer worldserver = minecraftserver.getWorld(i); ++ // WorldServer worldserver1 = minecraftserver.getWorld(dimensionIn); ++ WorldServer exitWorld = null; ++ if (this.dimension < CraftWorld.CUSTOM_DIMENSION_OFFSET) { // Plugins must specify exit from custom Bukkit worlds ++ // Only target existing worlds (compensate for allow-nether/allow-end as false) ++ for (WorldServer world : minecraftserver.worlds) { ++ if (world.dimension == dimensionIn) { ++ exitWorld = world; ++ } ++ } + } + +- this.world.removeEntity(this); +- this.isDead = false; +- this.world.profiler.startSection("reposition"); +- BlockPos blockpos; +- +- if (dimensionIn == 1 && teleporter.isVanilla()) +- { +- blockpos = worldserver1.getSpawnCoordinate(); ++ BlockPos blockposition = null; // PAIL: CHECK ++ Location enter = this.getBukkitEntity().getLocation(); ++ Location exit; ++ if (exitWorld != null) { ++ if (blockposition != null) { ++ exit = new Location(exitWorld.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } else { ++ exit = minecraftserver.getPlayerList().calculateTarget(enter, minecraftserver.getWorld(dimensionIn)); ++ } + } +- else +- { +- double moveFactor = worldserver.provider.getMovementFactor() / worldserver1.provider.getMovementFactor(); +- double d0 = MathHelper.clamp(this.posX * moveFactor, worldserver1.getWorldBorder().minX() + 16.0D, worldserver1.getWorldBorder().maxX() - 16.0D); +- double d1 = MathHelper.clamp(this.posZ * moveFactor, worldserver1.getWorldBorder().minZ() + 16.0D, worldserver1.getWorldBorder().maxZ() - 16.0D); +- double d2 = 8.0D; ++ else { ++ exit = null; ++ } ++ boolean useTravelAgent = exitWorld != null && !(this.dimension == 1 && exitWorld.dimension == 1); // don't use agent for custom worlds or return from THE_END + +- if (false && dimensionIn == -1) +- { +- d0 = MathHelper.clamp(d0 / 8.0D, worldserver1.getWorldBorder().minX() + 16.0D, worldserver1.getWorldBorder().maxX() - 16.0D); +- d1 = MathHelper.clamp(d1 / 8.0D, worldserver1.getWorldBorder().minZ() + 16.0D, worldserver1.getWorldBorder().maxZ() - 16.0D); +- } +- else if (false && dimensionIn == 0) +- { +- d0 = MathHelper.clamp(d0 * 8.0D, worldserver1.getWorldBorder().minX() + 16.0D, worldserver1.getWorldBorder().maxX() - 16.0D); +- d1 = MathHelper.clamp(d1 * 8.0D, worldserver1.getWorldBorder().minZ() + 16.0D, worldserver1.getWorldBorder().maxZ() - 16.0D); +- } ++ TravelAgent agent = exit != null ? (TravelAgent) ((CraftWorld) exit.getWorld()).getHandle().getDefaultTeleporter() : org.bukkit.craftbukkit.CraftTravelAgent.DEFAULT; // return arbitrary TA to compensate for implementation dependent plugins ++ boolean oldCanCreate = agent.getCanCreatePortal(); ++ agent.setCanCreatePortal(false); // General entities cannot create portals + +- d0 = (double)MathHelper.clamp((int)d0, -29999872, 29999872); +- d1 = (double)MathHelper.clamp((int)d1, -29999872, 29999872); +- float f = this.rotationYaw; +- this.setLocationAndAngles(d0, this.posY, d1, 90.0F, 0.0F); +- teleporter.placeEntity(worldserver1, this, f); +- blockpos = new BlockPos(this); ++ EntityPortalEvent event = new EntityPortalEvent(this.getBukkitEntity(), enter, exit, agent); ++ event.useTravelAgent(useTravelAgent); ++ event.getEntity().getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !this.isEntityAlive()) { ++ agent.setCanCreatePortal(oldCanCreate); ++ return null; + } ++ exit = event.useTravelAgent() ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo(); ++ agent.setCanCreatePortal(oldCanCreate); + +- worldserver.updateEntityWithOptionalForce(this, false); ++ // Need to make sure the profiler state is reset afterwards (but we still want to time the call) ++ Entity entity = this.teleportTo(exit, true); ++ this.world.profiler.endSection(); ++ return entity; ++ } ++ return null; ++ } ++ ++ public Entity teleportTo(Location exit, boolean portal) { ++ if (true) { // Paper ++ WorldServer worldserver = ((CraftWorld) getBukkitEntity().getLocation().getWorld()).getHandle(); ++ WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle(); ++ int i = worldserver1.dimension; ++ this.dimension = i; ++ this.world.removeEntity(this); // Paper - Fully remove entity, can't have dupes in the UUID map ++ this.isDead = false; ++ this.world.profiler.startSection("reposition"); ++ worldserver1.getMinecraftServer().getPlayerList().repositionEntity(this, exit, portal); + this.world.profiler.endStartSection("reloading"); + Entity entity = EntityList.newEntity(this.getClass(), worldserver1); + +@@ -2605,28 +2904,26 @@ + { + entity.copyDataFromOld(this); + +- if (i == 1 && dimensionIn == 1 && teleporter.isVanilla()) +- { +- BlockPos blockpos1 = worldserver1.getTopSolidOrLiquidBlock(worldserver1.getSpawnPoint()); +- entity.moveToBlockPosAndAngles(blockpos1, entity.rotationYaw, entity.rotationPitch); +- } +- else +- { +- entity.moveToBlockPosAndAngles(blockpos, entity.rotationYaw, entity.rotationPitch); +- } +- + boolean flag = entity.forceSpawn; + entity.forceSpawn = true; + worldserver1.spawnEntity(entity); + entity.forceSpawn = flag; + worldserver1.updateEntityWithOptionalForce(entity, false); ++ // CraftBukkit start - Forward the CraftEntity to the new entity ++ this.getBukkitEntity().setHandle(entity); ++ entity.bukkitEntity = this.getBukkitEntity(); ++ ++ if (this instanceof EntityLiving) { ++ ((EntityLiving) this).clearLeashed(true, false); // Unleash to prevent duping of leads. ++ } ++ // CraftBukkit end + } + + this.isDead = true; + this.world.profiler.endSection(); + worldserver.resetUpdateEntityTick(); + worldserver1.resetUpdateEntityTick(); +- this.world.profiler.endSection(); ++ // this.world.profiler.endSection(); // CraftBukkit: Moved up to keep balanced + return entity; + } + else +@@ -2755,6 +3052,11 @@ + + public void setCustomNameTag(String name) + { ++ // CraftBukkit start - Add a sane limit for name length ++ if (name.length() > 256) { ++ name = name.substring(0, 256); ++ } ++ // CraftBukkit end + this.dataManager.set(CUSTOM_NAME, name); + } + +@@ -2838,7 +3140,25 @@ + + public void setEntityBoundingBox(AxisAlignedBB bb) + { +- this.boundingBox = bb; ++// this.boundingBox = bb; ++ double a = bb.minX, ++ b = bb.minY, ++ c = bb.minZ, ++ d = bb.maxX, ++ e = bb.maxY, ++ f = bb.maxZ; ++ double len = bb.maxX - bb.minX; ++ if (len < 0) d = a; ++ if (len > 64) d = a + 64.0; ++ ++ len = bb.maxY - bb.minY; ++ if (len < 0) e = b; ++ if (len > 64) e = b + 64.0; ++ ++ len = bb.maxZ - bb.minZ; ++ if (len < 0) f = c; ++ if (len > 64) f = c + 64.0; ++ this.boundingBox = new AxisAlignedBB(a, b, c, d, e, f); + } + + public float getEyeHeight() +@@ -2870,6 +3190,11 @@ + return true; + } + ++ public boolean canUseCommand(int permLevel, String commandName, String perm) ++ { ++ return true; ++ } ++ + public BlockPos getPosition() + { + return new BlockPos(this.posX, this.posY + 0.5D, this.posZ); +@@ -3030,7 +3355,7 @@ + { + return ((net.minecraft.entity.item.EntityMinecart)this).getCartItem(); + } +- else if (this instanceof net.minecraft.entity.item.EntityBoat) ++ else if (this instanceof EntityBoat) + { + return new ItemStack(((EntityBoat)this).getItemBoat()); + } +@@ -3109,14 +3434,14 @@ + } + + @Override +- public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + return capabilities != null && capabilities.hasCapability(capability, facing); + } + + @Override + @Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + return capabilities == null ? null : capabilities.getCapability(capability, facing); + } +@@ -3319,7 +3644,7 @@ + return SoundCategory.NEUTRAL; + } + +- protected int getFireImmuneTicks() ++ public int getFireImmuneTicks() + { + return 1; + } diff --git a/patches/net/minecraft/entity/EntityAgeable.java.patch b/patches/net/minecraft/entity/EntityAgeable.java.patch new file mode 100644 index 00000000..8accf990 --- /dev/null +++ b/patches/net/minecraft/entity/EntityAgeable.java.patch @@ -0,0 +1,70 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityAgeable.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityAgeable.java +@@ -22,6 +22,33 @@ + private float ageWidth = -1.0F; + private float ageHeight; + ++ public boolean ageLocked; ++ ++ // Spigot start ++ @Override ++ public void inactiveTick() ++ { ++ super.inactiveTick(); ++ if ( this.world.isRemote || this.ageLocked ) ++ { // CraftBukkit ++ this.setScaleForAge( this.isChild() ); ++ } else ++ { ++ int i = this.getGrowingAge(); ++ ++ if ( i < 0 ) ++ { ++ ++i; ++ this.setGrowingAge( i ); ++ } else if ( i > 0 ) ++ { ++ --i; ++ this.setGrowingAge( i ); ++ } ++ } ++ } ++ // Spigot end ++ + public EntityAgeable(World worldIn) + { + super(worldIn); +@@ -48,7 +75,7 @@ + { + entityageable.setGrowingAge(-24000); + entityageable.setLocationAndAngles(this.posX, this.posY, this.posZ, 0.0F, 0.0F); +- this.world.spawnEntity(entityageable); ++ this.world.spawnEntity(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); + + if (itemstack.hasDisplayName()) + { +@@ -154,6 +181,7 @@ + super.writeEntityToNBT(compound); + compound.setInteger("Age", this.getGrowingAge()); + compound.setInteger("ForcedAge", this.forcedAge); ++ compound.setBoolean("AgeLocked", this.ageLocked); + } + + public void readEntityFromNBT(NBTTagCompound compound) +@@ -161,6 +189,7 @@ + super.readEntityFromNBT(compound); + this.setGrowingAge(compound.getInteger("Age")); + this.forcedAge = compound.getInteger("ForcedAge"); ++ this.ageLocked = compound.getBoolean("AgeLocked"); + } + + public void notifyDataManagerChange(DataParameter key) +@@ -177,7 +206,7 @@ + { + super.onLivingUpdate(); + +- if (this.world.isRemote) ++ if (this.world.isRemote || ageLocked) + { + if (this.forcedAgeTimer > 0) + { diff --git a/patches/net/minecraft/entity/EntityAreaEffectCloud.java.patch b/patches/net/minecraft/entity/EntityAreaEffectCloud.java.patch new file mode 100644 index 00000000..bf28ad98 --- /dev/null +++ b/patches/net/minecraft/entity/EntityAreaEffectCloud.java.patch @@ -0,0 +1,126 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityAreaEffectCloud.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityAreaEffectCloud.java +@@ -2,10 +2,7 @@ + + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; +-import java.util.Iterator; +-import java.util.List; +-import java.util.Map; +-import java.util.UUID; ++import java.util.*; + import java.util.Map.Entry; + import javax.annotation.Nullable; + import net.minecraft.block.material.EnumPushReaction; +@@ -23,6 +20,8 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; + import net.minecraft.world.WorldServer; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; + + public class EntityAreaEffectCloud extends Entity + { +@@ -33,15 +32,15 @@ + private static final DataParameter PARTICLE_PARAM_1 = EntityDataManager.createKey(EntityAreaEffectCloud.class, DataSerializers.VARINT); + private static final DataParameter PARTICLE_PARAM_2 = EntityDataManager.createKey(EntityAreaEffectCloud.class, DataSerializers.VARINT); + private PotionType potion; +- private final List effects; ++ public final List effects; + private final Map reapplicationDelayMap; + private int duration; +- private int waitTime; +- private int reapplicationDelay; ++ public int waitTime; ++ public int reapplicationDelay; + private boolean colorSet; +- private int durationOnUse; +- private float radiusOnUse; +- private float radiusPerTick; ++ public int durationOnUse; ++ public float radiusOnUse; ++ public float radiusPerTick; + private EntityLivingBase owner; + private UUID ownerUniqueId; + +@@ -126,6 +125,23 @@ + } + } + ++ // CraftBukkit start accessor methods ++ public void refreshEffects() { ++ if (!this.colorSet) { ++ this.getDataManager().set(EntityAreaEffectCloud.COLOR, PotionUtils.getPotionColorFromEffectList(PotionUtils.mergeEffects(this.potion, (Collection) this.effects))); ++ } ++ } ++ ++ public String getType() { ++ return (PotionType.REGISTRY.getNameForObject(this.potion)).toString(); ++ } ++ ++ public void setType(String string) { ++ setPotion(PotionType.REGISTRY.getObject(new ResourceLocation(string))); ++ } ++ // CraftBukkit end ++ ++ + public int getColor() + { + return ((Integer)this.getDataManager().get(COLOR)).intValue(); +@@ -192,7 +208,12 @@ + super.onUpdate(); + boolean flag = this.shouldIgnoreRadius(); + float f = this.getRadius(); +- ++ // Paper start - fix MC-114618 ++ if (f < 0.5F) { ++ this.setDead(); ++ return; ++ } ++ // Paper end + if (this.world.isRemote) + { + EnumParticleTypes enumparticletypes = this.getParticle(); +@@ -284,11 +305,13 @@ + { + f += this.radiusPerTick; + +- if (f < 0.5F) +- { +- this.setDead(); +- return; +- } ++ // Paper start - moved up - fix MC-114618 ++ //if (f < 0.5F) ++ //{ ++ // this.setDead(); ++ // return; ++ //} ++ // Paper end + + this.setRadius(f); + } +@@ -326,6 +349,7 @@ + + if (!list.isEmpty()) + { ++ List entities = new ArrayList<>(); + for (EntityLivingBase entitylivingbase : list) + { + if (!this.reapplicationDelayMap.containsKey(entitylivingbase) && entitylivingbase.canBeHitWithPotion()) +@@ -336,6 +360,15 @@ + + if (d2 <= (double)(f * f)) + { ++ entities.add((LivingEntity) entitylivingbase.getBukkitEntity()); ++ } ++ } ++ } ++ org.bukkit.event.entity.AreaEffectCloudApplyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callAreaEffectCloudApplyEvent(this, entities); ++ if (true) { // Preserve NMS spacing and bracket count for smallest diff ++ for (LivingEntity entity : event.getAffectedEntities()) { ++ if (entity instanceof CraftLivingEntity) { ++ EntityLivingBase entitylivingbase = ((CraftLivingEntity) entity).getHandle(); + this.reapplicationDelayMap.put(entitylivingbase, Integer.valueOf(this.ticksExisted + this.reapplicationDelay)); + + for (PotionEffect potioneffect : potions) diff --git a/patches/net/minecraft/entity/EntityCreature.java.patch b/patches/net/minecraft/entity/EntityCreature.java.patch new file mode 100644 index 00000000..a659e8f1 --- /dev/null +++ b/patches/net/minecraft/entity/EntityCreature.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityCreature.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityCreature.java +@@ -7,6 +7,7 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.Vec3d; + import net.minecraft.world.World; ++import org.bukkit.event.entity.EntityUnleashEvent; + + public abstract class EntityCreature extends EntityLiving + { +@@ -93,6 +94,7 @@ + { + if (f > 10.0F) + { ++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); + this.clearLeashed(true, true); + } + +@@ -103,6 +105,7 @@ + + if (f > 10.0F) + { ++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); + this.clearLeashed(true, true); + this.tasks.disableControlFlag(1); + } diff --git a/patches/net/minecraft/entity/EntityHanging.java.patch b/patches/net/minecraft/entity/EntityHanging.java.patch new file mode 100644 index 00000000..f8e90ed8 --- /dev/null +++ b/patches/net/minecraft/entity/EntityHanging.java.patch @@ -0,0 +1,207 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityHanging.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityHanging.java +@@ -3,6 +3,7 @@ + import com.google.common.base.Predicate; + import javax.annotation.Nullable; + import net.minecraft.block.BlockRedstoneDiode; ++import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.effect.EntityLightningBolt; + import net.minecraft.entity.item.EntityItem; +@@ -18,6 +19,9 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; + import org.apache.commons.lang3.Validate; ++import org.bukkit.entity.Hanging; ++import org.bukkit.event.hanging.HangingBreakByEntityEvent; ++import org.bukkit.event.hanging.HangingBreakEvent; + + public abstract class EntityHanging extends Entity + { +@@ -29,7 +33,7 @@ + } + }; + private int tickCounter1; +- protected BlockPos hangingPosition; ++ public BlockPos hangingPosition; + @Nullable + public EnumFacing facingDirection; + +@@ -49,7 +53,7 @@ + { + } + +- protected void updateFacingWithBoundingBox(EnumFacing facingDirectionIn) ++ public void updateFacingWithBoundingBox(EnumFacing facingDirectionIn) + { + Validate.notNull(facingDirectionIn); + Validate.isTrue(facingDirectionIn.getAxis().isHorizontal()); +@@ -63,42 +67,50 @@ + { + if (this.facingDirection != null) + { +- double d0 = (double)this.hangingPosition.getX() + 0.5D; +- double d1 = (double)this.hangingPosition.getY() + 0.5D; +- double d2 = (double)this.hangingPosition.getZ() + 0.5D; +- double d3 = 0.46875D; +- double d4 = this.offs(this.getWidthPixels()); +- double d5 = this.offs(this.getHeightPixels()); +- d0 = d0 - (double)this.facingDirection.getFrontOffsetX() * 0.46875D; +- d2 = d2 - (double)this.facingDirection.getFrontOffsetZ() * 0.46875D; +- d1 = d1 + d5; +- EnumFacing enumfacing = this.facingDirection.rotateYCCW(); +- d0 = d0 + d4 * (double)enumfacing.getFrontOffsetX(); +- d2 = d2 + d4 * (double)enumfacing.getFrontOffsetZ(); +- this.posX = d0; +- this.posY = d1; +- this.posZ = d2; +- double d6 = (double)this.getWidthPixels(); +- double d7 = (double)this.getHeightPixels(); +- double d8 = (double)this.getWidthPixels(); ++ // CraftBukkit start code moved in to calculateBoundingBox ++ this.setEntityBoundingBox(calculateBoundingBox(this, this.hangingPosition, this.facingDirection, this.getWidthPixels(), this.getHeightPixels())); ++ // CraftBukkit end ++ } ++ } + +- if (this.facingDirection.getAxis() == EnumFacing.Axis.Z) +- { +- d8 = 1.0D; +- } +- else +- { +- d6 = 1.0D; +- } ++ // CraftBukkit start - break out BB calc into own method ++ public static AxisAlignedBB calculateBoundingBox(Entity entity, BlockPos blockPosition, EnumFacing direction, int width, int height) { ++ double d0 = (double) blockPosition.getX() + 0.5D; ++ double d1 = (double) blockPosition.getY() + 0.5D; ++ double d2 = (double) blockPosition.getZ() + 0.5D; ++ double d3 = 0.46875D; ++ double d4 = offs(width); ++ double d5 = offs(height); + +- d6 = d6 / 32.0D; +- d7 = d7 / 32.0D; +- d8 = d8 / 32.0D; +- this.setEntityBoundingBox(new AxisAlignedBB(d0 - d6, d1 - d7, d2 - d8, d0 + d6, d1 + d7, d2 + d8)); ++ d0 -= (double) direction.getFrontOffsetX() * 0.46875D; ++ d2 -= (double) direction.getFrontOffsetZ() * 0.46875D; ++ d1 += d5; ++ EnumFacing enumdirection = direction.rotateYCCW(); ++ ++ d0 += d4 * (double) enumdirection.getFrontOffsetX(); ++ d2 += d4 * (double) enumdirection.getFrontOffsetZ(); ++ if (entity != null) { ++ entity.posX = d0; ++ entity.posY = d1; ++ entity.posZ = d2; + } ++ double d6 = (double) width; ++ double d7 = (double) height; ++ double d8 = (double) width; ++ ++ if (direction.getAxis() == EnumFacing.Axis.Z) { ++ d8 = 1.0D; ++ } else { ++ d6 = 1.0D; ++ } ++ ++ d6 /= 32.0D; ++ d7 /= 32.0D; ++ d8 /= 32.0D; ++ return new AxisAlignedBB(d0 - d6, d1 - d7, d2 - d8, d0 + d6, d1 + d7, d2 + d8); + } + +- private double offs(int p_190202_1_) ++ private static double offs(int p_190202_1_) // CraftBukkit - static + { + return p_190202_1_ % 32 == 0 ? 0.5D : 0.0D; + } +@@ -109,12 +121,30 @@ + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + +- if (this.tickCounter1++ == 100 && !this.world.isRemote) ++ if (this.tickCounter1++ == this.world.spigotConfig.hangingTickFrequency && !this.world.isRemote) // Spigot + { + this.tickCounter1 = 0; + + if (!this.isDead && !this.onValidSurface()) + { ++ // CraftBukkit start - fire break events ++ Material material = this.world.getBlockState(new BlockPos(this)).getMaterial(); ++ HangingBreakEvent.RemoveCause cause; ++ ++ if (!material.equals(Material.AIR)) { ++ // TODO: This feels insufficient to catch 100% of suffocation cases ++ cause = HangingBreakEvent.RemoveCause.OBSTRUCTION; ++ } else { ++ cause = HangingBreakEvent.RemoveCause.PHYSICS; ++ } ++ ++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), cause); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (isDead || event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.setDead(); + this.onBroken((Entity)null); + } +@@ -183,6 +213,20 @@ + { + if (!this.isDead && !this.world.isRemote) + { ++ // CraftBukkit start - fire break events ++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.DEFAULT); ++ if (source.getTrueSource() != null) { ++ event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), source.getTrueSource() == null ? null : source.getTrueSource().getBukkitEntity(), source.isExplosion() ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY); ++ } else if (source.isExplosion()) { ++ event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.EXPLOSION); ++ } ++ ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (this.isDead || event.isCancelled()) { ++ return true; ++ } ++ // CraftBukkit end + this.setDead(); + this.markVelocityChanged(); + this.onBroken(source.getTrueSource()); +@@ -196,6 +240,15 @@ + { + if (!this.world.isRemote && !this.isDead && x * x + y * y + z * z > 0.0D) + { ++ // CraftBukkit start - fire break events ++ // TODO - Does this need its own cause? Seems to only be triggered by pistons ++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.PHYSICS); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (this.isDead || event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.setDead(); + this.onBroken((Entity)null); + } +@@ -203,7 +256,7 @@ + + public void addVelocity(double x, double y, double z) + { +- if (!this.world.isRemote && !this.isDead && x * x + y * y + z * z > 0.0D) ++ if (false && !this.world.isRemote && !this.isDead && x * x + y * y + z * z > 0.0D) // CraftBukkit - not needed + { + this.setDead(); + this.onBroken((Entity)null); +@@ -296,7 +349,7 @@ + return this.getRotatedYaw(transformMirror.toRotation(this.facingDirection)); + } + +- public void onStruckByLightning(EntityLightningBolt lightningBolt) ++ public void onStruckByLightning(@Nullable EntityLightningBolt lightningBolt) + { + } + } diff --git a/patches/net/minecraft/entity/EntityLeashKnot.java.patch b/patches/net/minecraft/entity/EntityLeashKnot.java.patch new file mode 100644 index 00000000..077b9ffc --- /dev/null +++ b/patches/net/minecraft/entity/EntityLeashKnot.java.patch @@ -0,0 +1,63 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityLeashKnot.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityLeashKnot.java +@@ -4,8 +4,10 @@ + import javax.annotation.Nullable; + import net.minecraft.block.BlockFence; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.SoundEvents; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.play.server.SPacketEntityAttach; + import net.minecraft.util.EnumFacing; + import net.minecraft.util.EnumHand; + import net.minecraft.util.math.AxisAlignedBB; +@@ -14,6 +16,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityLeashKnot extends EntityHanging + { +@@ -105,6 +108,12 @@ + { + if (entityliving.getLeashed() && entityliving.getLeashHolder() == player) + { ++ // CraftBukkit start ++ if (CraftEventFactory.callPlayerLeashEntityEvent(entityliving, this, player).isCancelled()) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketEntityAttach(entityliving, entityliving.getLeashHolder())); ++ continue; ++ } ++ // CraftBukkit end + entityliving.setLeashHolder(this, true); + flag = true; + } +@@ -112,17 +121,23 @@ + + if (!flag) + { +- this.setDead(); +- +- if (player.capabilities.isCreativeMode) +- { ++ // CraftBukkit start - Move below ++ if (true || player.capabilities.isCreativeMode) { // CraftBukkit - Process for non-creative as well ++ boolean die = true; + for (EntityLiving entityliving1 : list) + { + if (entityliving1.getLeashed() && entityliving1.getLeashHolder() == this) + { +- entityliving1.clearLeashed(true, false); ++ if (CraftEventFactory.callPlayerUnleashEntityEvent(entityliving1, player).isCancelled()) { ++ die = false; ++ continue; ++ } ++ entityliving1.clearLeashed(true, !player.capabilities.isCreativeMode); // false -> survival mode boolean + } + } ++ if (die) { ++ this.setDead(); ++ } + } + } + diff --git a/patches/net/minecraft/entity/EntityLiving.java.patch b/patches/net/minecraft/entity/EntityLiving.java.patch new file mode 100644 index 00000000..45c7200d --- /dev/null +++ b/patches/net/minecraft/entity/EntityLiving.java.patch @@ -0,0 +1,290 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityLiving.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityLiving.java +@@ -8,25 +8,18 @@ + import javax.annotation.Nullable; + import net.minecraft.block.state.IBlockState; + import net.minecraft.enchantment.EnchantmentHelper; +-import net.minecraft.entity.ai.EntityAITasks; +-import net.minecraft.entity.ai.EntityJumpHelper; +-import net.minecraft.entity.ai.EntityLookHelper; +-import net.minecraft.entity.ai.EntityMoveHelper; +-import net.minecraft.entity.ai.EntitySenses; ++import net.minecraft.entity.ai.*; + import net.minecraft.entity.ai.attributes.AttributeModifier; + import net.minecraft.entity.item.EntityBoat; + import net.minecraft.entity.item.EntityItem; + import net.minecraft.entity.monster.EntityGhast; + import net.minecraft.entity.monster.IMob; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.init.Items; + import net.minecraft.inventory.EntityEquipmentSlot; +-import net.minecraft.item.Item; +-import net.minecraft.item.ItemArmor; +-import net.minecraft.item.ItemBow; +-import net.minecraft.item.ItemStack; +-import net.minecraft.item.ItemSword; ++import net.minecraft.item.*; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagFloat; + import net.minecraft.nbt.NBTTagList; +@@ -37,13 +30,7 @@ + import net.minecraft.pathfinding.PathNavigate; + import net.minecraft.pathfinding.PathNavigateGround; + import net.minecraft.pathfinding.PathNodeType; +-import net.minecraft.util.DamageSource; +-import net.minecraft.util.EnumHand; +-import net.minecraft.util.EnumHandSide; +-import net.minecraft.util.EnumParticleTypes; +-import net.minecraft.util.NonNullList; +-import net.minecraft.util.ResourceLocation; +-import net.minecraft.util.SoundEvent; ++import net.minecraft.util.*; + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.FixTypes; + import net.minecraft.util.datafix.walkers.ItemStackDataLists; +@@ -57,6 +44,13 @@ + import net.minecraft.world.storage.loot.LootTable; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.event.entity.EntityPickupItemEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++import org.bukkit.event.entity.EntityUnleashEvent; + + public abstract class EntityLiving extends EntityLivingBase + { +@@ -68,16 +62,15 @@ + protected EntityJumpHelper jumpHelper; + private final EntityBodyHelper bodyHelper; + protected PathNavigate navigator; +- public final EntityAITasks tasks; +- public final EntityAITasks targetTasks; ++ public EntityAITasks tasks; ++ public EntityAITasks targetTasks; + private EntityLivingBase attackTarget; + private final EntitySenses senses; + private final NonNullList inventoryHands = NonNullList.withSize(2, ItemStack.EMPTY); +- protected float[] inventoryHandsDropChances = new float[2]; ++ public float[] inventoryHandsDropChances = new float[2]; + private final NonNullList inventoryArmor = NonNullList.withSize(4, ItemStack.EMPTY); +- protected float[] inventoryArmorDropChances = new float[4]; +- private boolean canPickUpLoot; +- private boolean persistenceRequired; ++ public float[] inventoryArmorDropChances = new float[4]; ++ public boolean persistenceRequired; + private final Map mapPathPriority = Maps.newEnumMap(PathNodeType.class); + private ResourceLocation deathLootTable; + private long deathLootTableSeed; +@@ -103,6 +96,10 @@ + { + this.initEntityAI(); + } ++ ++ // CraftBukkit start - default persistance to type's persistance value ++ this.persistenceRequired = !canDespawn(); ++ // CraftBukkit end + } + + protected void initEntityAI() +@@ -171,8 +168,37 @@ + { + this.attackTarget = entitylivingbaseIn; + net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(this, entitylivingbaseIn); ++ // CraftBukkit start - fire event ++ setAttackTarget(entitylivingbaseIn, EntityTargetEvent.TargetReason.UNKNOWN, true); + } + ++ public boolean setAttackTarget(@Nullable EntityLivingBase entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { ++ if (getAttackTarget() == entityliving) return false; ++ if (fireEvent) { ++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN && getAttackTarget() != null && entityliving == null) { ++ reason = getAttackTarget().isEntityAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED; ++ } ++ CraftLivingEntity ctarget = null; ++ if (entityliving != null) { ++ ctarget = (CraftLivingEntity) entityliving.getBukkitEntity(); ++ } ++ EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason); ++ world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ if (event.getTarget() != null) { ++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle(); ++ } else { ++ entityliving = null; ++ } ++ } ++ this.attackTarget = entityliving; ++ return true; ++ // CraftBukkit end ++ } ++ + public boolean canAttackClass(Class cls) + { + return cls != EntityGhast.class; +@@ -355,7 +381,7 @@ + public void writeEntityToNBT(NBTTagCompound compound) + { + super.writeEntityToNBT(compound); +- compound.setBoolean("CanPickUpLoot", this.canPickUpLoot()); ++ compound.setBoolean("CanPickUpLoot", this.thisisatestof()); + compound.setBoolean("PersistenceRequired", this.persistenceRequired); + NBTTagList nbttaglist = new NBTTagList(); + +@@ -447,12 +473,21 @@ + { + super.readEntityFromNBT(compound); + ++ // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it + if (compound.hasKey("CanPickUpLoot", 1)) + { +- this.setCanPickUpLoot(compound.getBoolean("CanPickUpLoot")); ++ // this.idkwhyreyoudoingthis(compound.getBoolean("CanPickUpLoot")); ++ boolean data = compound.getBoolean("CanPickUpLoot"); ++ if (isLevelAtLeast(compound, 1) || data) { ++ this.idkwhyreyoudoingthis(data); ++ } + } + +- this.persistenceRequired = compound.getBoolean("PersistenceRequired"); ++ // this.persistenceRequired = compound.getBoolean("PersistenceRequired"); ++ boolean data = compound.getBoolean("PersistenceRequired"); ++ if (isLevelAtLeast(compound, 1) || data) { ++ this.persistenceRequired = data; ++ } + + if (compound.hasKey("ArmorItems", 9)) + { +@@ -577,7 +612,7 @@ + super.onLivingUpdate(); + this.world.profiler.startSection("looting"); + +- if (!this.world.isRemote && this.canPickUpLoot() && !this.dead && net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)) ++ if (!this.world.isRemote && this.thisisatestof() && !this.dead && net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)) + { + for (EntityItem entityitem : this.world.getEntitiesWithinAABB(EntityItem.class, this.getEntityBoundingBox().grow(1.0D, 0.0D, 1.0D))) + { +@@ -653,8 +688,14 @@ + } + } + +- if (flag && this.canEquipItem(itemstack)) +- { ++ // if (flag && this.canEquipItem(itemstack)) { ++ boolean canPickup = flag && this.canEquipItem(itemstack); ++ ++ EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((LivingEntity) getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity(), 0); ++ entityEvent.setCancelled(!canPickup); ++ this.world.getServer().getPluginManager().callEvent(entityEvent); ++ canPickup = !entityEvent.isCancelled(); ++ if (canPickup) { + double d0; + + switch (entityequipmentslot.getSlotType()) +@@ -696,7 +737,7 @@ + return true; + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return true; + } +@@ -730,12 +771,12 @@ + double d2 = entity.posZ - this.posZ; + double d3 = d0 * d0 + d1 * d1 + d2 * d2; + +- if (this.canDespawn() && d3 > 16384.0D) ++ if (/*this.canDespawn() && */d3 > 16384.0D) // CraftBukkit - remove canDespawn() check + { + this.setDead(); + } + +- if (this.idleTime > 600 && this.rand.nextInt(800) == 0 && d3 > 1024.0D && this.canDespawn()) ++ if (this.idleTime > 600 && this.rand.nextInt(800) == 0 && d3 > 1024.0D/* && this.canDespawn()*/) // CraftBukkit - remove canDespawn() check + { + this.setDead(); + } +@@ -753,6 +794,12 @@ + this.world.profiler.startSection("checkDespawn"); + this.despawnEntity(); + this.world.profiler.endSection(); ++ // Spigot Start ++ if (this.fromMobSpawner) ++ { ++ return; ++ } ++ // Spigot End + this.world.profiler.startSection("sensing"); + this.senses.clearSensingCache(); + this.world.profiler.endSection(); +@@ -1190,14 +1237,14 @@ + } + } + +- public boolean canPickUpLoot() ++ public boolean thisisatestof() + { +- return this.canPickUpLoot; ++ return thisisatest; + } + +- public void setCanPickUpLoot(boolean canPickup) ++ public void idkwhyreyoudoingthis(boolean canPickup) + { +- this.canPickUpLoot = canPickup; ++ thisisatest = canPickup; + } + + public boolean isNoDespawnRequired() +@@ -1209,6 +1256,10 @@ + { + if (this.getLeashed() && this.getLeashHolder() == player) + { ++ if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player).isCancelled()) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketEntityAttach(this, this.getLeashHolder())); ++ return false; ++ } + this.clearLeashed(true, !player.capabilities.isCreativeMode); + return true; + } +@@ -1218,6 +1269,10 @@ + + if (itemstack.getItem() == Items.LEAD && this.canBeLeashedTo(player)) + { ++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player).isCancelled()) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketEntityAttach(this, this.getLeashHolder())); ++ return false; ++ } + this.setLeashHolder(player, true); + itemstack.shrink(1); + return true; +@@ -1245,11 +1300,13 @@ + { + if (!this.isEntityAlive()) + { ++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH)); + this.clearLeashed(true, true); + } + + if (this.leashHolder == null || this.leashHolder.isDead) + { ++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.HOLDER_GONE)); + this.clearLeashed(true, true); + } + } +@@ -1348,6 +1405,7 @@ + } + else + { ++ this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN)); + this.clearLeashed(false, true); + } + } diff --git a/patches/net/minecraft/entity/EntityLivingBase.java.patch b/patches/net/minecraft/entity/EntityLivingBase.java.patch new file mode 100644 index 00000000..f78d4922 --- /dev/null +++ b/patches/net/minecraft/entity/EntityLivingBase.java.patch @@ -0,0 +1,875 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityLivingBase.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityLivingBase.java +@@ -1,7 +1,11 @@ + package net.minecraft.entity; + ++import com.google.common.base.Function; + import com.google.common.base.Objects; ++import com.google.common.collect.Lists; + import com.google.common.collect.Maps; ++ ++import java.util.ArrayList; + import java.util.Collection; + import java.util.ConcurrentModificationException; + import java.util.Iterator; +@@ -19,6 +23,7 @@ + import net.minecraft.block.state.IBlockState; + import net.minecraft.enchantment.EnchantmentFrostWalker; + import net.minecraft.enchantment.EnchantmentHelper; ++import net.minecraft.entity.ai.EntityAISit; + import net.minecraft.entity.ai.attributes.AbstractAttributeMap; + import net.minecraft.entity.ai.attributes.AttributeMap; + import net.minecraft.entity.ai.attributes.AttributeModifier; +@@ -28,7 +33,8 @@ + import net.minecraft.entity.item.EntityItem; + import net.minecraft.entity.item.EntityXPOrb; + import net.minecraft.entity.passive.AbstractHorse; +-import net.minecraft.entity.passive.EntityWolf; ++import net.minecraft.entity.passive.EntityAnimal; ++import net.minecraft.entity.passive.EntityTameable; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.entity.projectile.EntityArrow; +@@ -43,7 +49,10 @@ + import net.minecraft.item.ItemArmor; + import net.minecraft.item.ItemElytra; + import net.minecraft.item.ItemStack; ++import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagFloat; ++import net.minecraft.nbt.NBTTagInt; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.network.datasync.DataParameter; + import net.minecraft.network.datasync.DataSerializers; +@@ -76,21 +85,32 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.attribute.CraftAttributeMap; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.EntityDamageEvent; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.EntityResurrectEvent; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.event.player.PlayerItemConsumeEvent; + + public abstract class EntityLivingBase extends Entity + { + private static final Logger LOGGER = LogManager.getLogger(); + private static final UUID SPRINTING_SPEED_BOOST_ID = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D"); + private static final AttributeModifier SPRINTING_SPEED_BOOST = (new AttributeModifier(SPRINTING_SPEED_BOOST_ID, "Sprinting speed boost", 0.30000001192092896D, 2)).setSaved(false); +- public static final net.minecraft.entity.ai.attributes.IAttribute SWIM_SPEED = new net.minecraft.entity.ai.attributes.RangedAttribute(null, "forge.swimSpeed", 1.0D, 0.0D, 1024.0D).setShouldWatch(true); ++ public static final IAttribute SWIM_SPEED = new net.minecraft.entity.ai.attributes.RangedAttribute(null, "forge.swimSpeed", 1.0D, 0.0D, 1024.0D).setShouldWatch(true); + protected static final DataParameter HAND_STATES = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.BYTE); +- private static final DataParameter HEALTH = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.FLOAT); ++ public static final DataParameter HEALTH = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.FLOAT); + private static final DataParameter POTION_EFFECTS = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.VARINT); + private static final DataParameter HIDE_PARTICLES = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.BOOLEAN); + private static final DataParameter ARROW_COUNT_IN_ENTITY = EntityDataManager.createKey(EntityLivingBase.class, DataSerializers.VARINT); + private AbstractAttributeMap attributeMap; +- private final CombatTracker _combatTracker = new CombatTracker(this); +- private final Map activePotionsMap = Maps.newHashMap(); ++ public CombatTracker _combatTracker = new CombatTracker(this); ++ public final Map activePotionsMap = Maps.newHashMap(); // Spigot + private final NonNullList handInventory = NonNullList.withSize(2, ItemStack.EMPTY); + private final NonNullList armorArray = NonNullList.withSize(4, ItemStack.EMPTY); + public boolean isSwingInProgress; +@@ -117,7 +137,7 @@ + public float rotationYawHead; + public float prevRotationYawHead; + public float jumpMovementFactor = 0.02F; +- protected EntityPlayer attackingPlayer; ++ public EntityPlayer attackingPlayer; + protected int recentlyHit; + protected boolean dead; + protected int idleTime; +@@ -127,7 +147,7 @@ + protected float prevMovedDistance; + protected float unused180; + protected int scoreValue; +- protected float lastDamage; ++ public float lastDamage; + protected boolean isJumping; + public float moveStrafing; + public float moveVertical; +@@ -139,9 +159,9 @@ + protected double interpTargetZ; + protected double interpTargetYaw; + protected double interpTargetPitch; +- private boolean potionsNeedUpdate = true; +- private EntityLivingBase revengeTarget; +- private int revengeTimer; ++ public boolean potionsNeedUpdate = true; ++ public EntityLivingBase revengeTarget; ++ public int revengeTimer; + private EntityLivingBase lastAttackedEntity; + private int lastAttackedEntityTime; + private float landMovementFactor; +@@ -154,6 +174,23 @@ + private DamageSource lastDamageSource; + private long lastDamageStamp; + ++ // CraftBukkit start ++ public int expToDrop; ++ public int maxAirTicks = 300; ++ public ArrayList drops = new ArrayList<>(); ++ public org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; ++ public boolean collides = true; ++ public boolean thisisatest; ++ // CraftBukkit end ++ ++ // Spigot start ++ public void inactiveTick() ++ { ++ super.inactiveTick(); ++ ++this.idleTime; // Above all the floats ++ } ++ // Spigot end ++ + public void onKillCommand() + { + this.attackEntityFrom(DamageSource.OUT_OF_WORLD, Float.MAX_VALUE); +@@ -163,7 +200,9 @@ + { + super(worldIn); + this.applyEntityAttributes(); +- this.setHealth(this.getMaxHealth()); ++ // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor ++ // this.setHealth(this.getMaxHealth()); ++ this.dataManager.set(EntityLiving.HEALTH, (float) this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).getAttributeValue()); + this.preventEntitySpawning = true; + this.randomUnused1 = (float)((Math.random() + 1.0D) * 0.009999999776482582D); + this.setPosition(this.posX, this.posY, this.posZ); +@@ -207,8 +246,17 @@ + { + double d0 = Math.min((double)(0.2F + f / 15.0F), 2.5D); + int i = (int)(150.0D * d0); +- if (!state.getBlock().addLandingEffects(state, (WorldServer)this.world, pos, state, this, i)) +- ((WorldServer)this.world).spawnParticle(EnumParticleTypes.BLOCK_DUST, this.posX, this.posY, this.posZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, Block.getStateId(state)); ++ if (!state.getBlock().addLandingEffects(state, (WorldServer)this.world, pos, state, this, i)) { ++ // ((WorldServer) this.world).spawnParticle(EnumParticleTypes.BLOCK_DUST, this.posX, this.posY, this.posZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, Block.getStateId(state)); ++ // TODO: Is it correct to perform this code inside this if-statement? ++ // CraftBukkit start - visiblity api ++ if (this instanceof EntityPlayer) { ++ ((WorldServer) this.world).sendParticles((EntityPlayerMP) this, EnumParticleTypes.BLOCK_DUST, false, this.posX, this.posY, this.posZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, new int[]{Block.getStateId(state)}); ++ } else { ++ ((WorldServer) this.world).spawnParticle(EnumParticleTypes.BLOCK_DUST, this.posX, this.posY, this.posZ, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, new int[]{Block.getStateId(state)}); ++ } ++ // CraftBukkit end ++ } + } + } + +@@ -258,9 +306,13 @@ + + if (this.isEntityAlive()) + { +- if (!this.isInsideOfMaterial(Material.WATER)) +- { +- this.setAir(300); ++ if (!this.isInsideOfMaterial(Material.WATER)) { ++ // this.setAir(300); ++ // CraftBukkit start - Only set if needed to work around a DataWatcher inefficiency ++ if (this.getAir() != 300) { ++ this.setAir(maxAirTicks); ++ } ++ // CraftBukkit end + } + else + { +@@ -359,6 +411,18 @@ + this.world.profiler.endSection(); + } + ++ // CraftBukkit start ++ public int getExpReward() { ++ int exp = this.getExperiencePoints(this.attackingPlayer); ++ ++ if (!this.world.isRemote && (this.recentlyHit > 0 || this.isPlayer()) && this.canDropLoot() && this.world.getGameRules().getBoolean("doMobLoot")) { ++ return exp; ++ } else { ++ return 0; ++ } ++ } ++ // CraftBukkit end ++ + protected void frostWalk(BlockPos pos) + { + int i = EnchantmentHelper.getMaxEnchantmentLevel(Enchantments.FROST_WALKER, this); +@@ -378,11 +442,10 @@ + { + ++this.deathTime; + +- if (this.deathTime == 20) ++ if (this.deathTime >= 20 && !this.isDead) // CraftBukkit - (this.deathTime == 20) -> (this.deathTime >= 20 && !this.isDead) + { +- if (!this.world.isRemote && (this.isPlayer() || this.recentlyHit > 0 && this.canDropLoot() && this.world.getGameRules().getBoolean("doMobLoot"))) +- { +- int i = this.getExperiencePoints(this.attackingPlayer); ++ // if (!this.world.isRemote && (this.isPlayer() || this.recentlyHit > 0 && this.canDropLoot() && this.world.getGameRules().getBoolean("doMobLoot"))) { ++ int i = this.expToDrop; + i = net.minecraftforge.event.ForgeEventFactory.getExperienceDrop(this, this.attackingPlayer, i); + while (i > 0) + { +@@ -390,7 +453,7 @@ + i -= j; + this.world.spawnEntity(new EntityXPOrb(this.world, this.posX, this.posY, this.posZ, j)); + } +- } ++ this.expToDrop = 0; + + this.setDead(); + +@@ -445,6 +508,7 @@ + { + this.revengeTarget = livingBase; + this.revengeTimer = this.ticksExisted; ++ net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(this, livingBase); + } + + public EntityLivingBase getLastAttackedEntity() +@@ -566,6 +630,17 @@ + } + } + ++ // CraftBukkit start ++ if (compound.hasKey("Bukkit.MaxHealth")) { ++ NBTBase nbtbase = compound.getTag("Bukkit.MaxHealth"); ++ if (nbtbase.getId() == 5) { ++ this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(((NBTTagFloat) nbtbase).getDouble()); ++ } else if (nbtbase.getId() == 3) { ++ this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(((NBTTagInt) nbtbase).getDouble()); ++ } ++ } ++ // CraftBukkit end ++ + if (compound.hasKey("Health", 99)) + { + this.setHealth(compound.getFloat("Health")); +@@ -592,9 +667,15 @@ + } + } + ++ // CraftBukkit start ++ private boolean isTickingEffects = false; ++ private List effectsToProcess = Lists.newArrayList(); ++ // CraftBukkit end ++ + protected void updatePotionEffects() + { + Iterator iterator = this.activePotionsMap.keySet().iterator(); ++ isTickingEffects = true; // CraftBukkit + + try + { +@@ -622,6 +703,18 @@ + ; + } + ++ // CraftBukkit start ++ isTickingEffects = false; ++ for (Object e : effectsToProcess) { ++ if (e instanceof PotionEffect) { ++ addPotionEffect((PotionEffect) e); ++ } else { ++ removePotionEffect((Potion) e); ++ } ++ } ++ effectsToProcess.clear(); ++ // CraftBukkit end ++ + if (this.potionsNeedUpdate) + { + if (!this.world.isRemote) +@@ -712,6 +805,7 @@ + if(net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.living.PotionEvent.PotionRemoveEvent(this, effect))) continue; + + this.onFinishedPotionEffect(effect); ++ + iterator.remove(); + } + } +@@ -740,6 +834,12 @@ + + public void addPotionEffect(PotionEffect potioneffectIn) + { ++ // CraftBukkit start ++ if (isTickingEffects) { ++ effectsToProcess.add(potioneffectIn); ++ return; ++ } ++ // CraftBukkit end + if (this.isPotionApplicable(potioneffectIn)) + { + PotionEffect potioneffect = this.activePotionsMap.get(potioneffectIn.getPotion()); +@@ -784,6 +884,12 @@ + @Nullable + public PotionEffect removeActivePotionEffect(@Nullable Potion potioneffectin) + { ++ // CraftBukkit start ++ if (isTickingEffects) { ++ effectsToProcess.add(potioneffectin); ++ return null; ++ } ++ // CraftBukkit end + return this.activePotionsMap.remove(potioneffectin); + } + +@@ -830,25 +936,57 @@ + } + } + ++ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained + public void heal(float healAmount) + { ++ heal(healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM); ++ } ++ ++ public void heal(float healAmount, EntityRegainHealthEvent.RegainReason regainReason) { + healAmount = net.minecraftforge.event.ForgeEventFactory.onLivingHeal(this, healAmount); + if (healAmount <= 0) return; + float f = this.getHealth(); + + if (f > 0.0F) + { +- this.setHealth(f + healAmount); ++ // this.setHealth(f + healAmount); ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), healAmount, regainReason); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setHealth((float) (this.getHealth() + event.getAmount())); ++ } + } + } + + public final float getHealth() + { ++ // CraftBukkit start - Use unscaled health ++ if (this instanceof EntityPlayer) { ++ return (float) ((EntityPlayer) this).getBukkitEntity().getHealth(); ++ } ++ // CraftBukkit end + return ((Float)this.dataManager.get(HEALTH)).floatValue(); + } + + public void setHealth(float health) + { ++ // CraftBukkit start - Handle scaled health ++ if (this instanceof EntityPlayer) { ++ org.bukkit.craftbukkit.entity.CraftPlayer player = ((EntityPlayerMP) this).getBukkitEntity(); ++ // Squeeze ++ if (health < 0.0F) { ++ player.setRealHealth(0.0D); ++ } else if (health > player.getMaxHealth()) { ++ player.setRealHealth(player.getMaxHealth()); ++ } else { ++ player.setRealHealth(health); ++ } ++ ++ player.updateScaledHealth(); ++ return; ++ } ++ // CraftBukkit end + this.dataManager.set(HEALTH, Float.valueOf(MathHelper.clamp(health, 0.0F, this.getMaxHealth()))); + } + +@@ -879,15 +1017,17 @@ + { + float f = amount; + +- if ((source == DamageSource.ANVIL || source == DamageSource.FALLING_BLOCK) && !this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()) ++ // CraftBukkit - Moved into damageEntity_CB(DamageSource, float) ++ if (false && (source == DamageSource.ANVIL || source == DamageSource.FALLING_BLOCK) && !this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()) + { + this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).damageItem((int)(amount * 4.0F + this.rand.nextFloat() * amount * 2.0F), this); + amount *= 0.75F; + } + +- boolean flag = false; ++ boolean flag = amount > 0.0F && this.canBlockDamageSource(source); // Copied from below + +- if (amount > 0.0F && this.canBlockDamageSource(source)) ++ // CraftBukkit - Moved into damageEntity0(DamageSource, float) ++ if (false && amount > 0.0F && this.canBlockDamageSource(source)) + { + this.damageShield(amount); + amount = 0.0F; +@@ -912,22 +1052,42 @@ + { + if (amount <= this.lastDamage) + { ++ this.forceExplosionKnockback = true; // CraftBukkit - SPIGOT-949 - for vanilla consistency, cooldown does not prevent explosion knockback + return false; + } + +- this.damageEntity(source, amount - this.lastDamage); ++ // CraftBukkit start ++ if (!this.damageEntity_CB(source, amount - this.lastDamage)) { ++ return false; ++ } ++ // CraftBukkit end + this.lastDamage = amount; + flag1 = false; + } + else + { ++ // CraftBukkit start ++ if (!this.damageEntity_CB(source, amount)) { ++ return false; ++ } ++ // CraftBukkit end + this.lastDamage = amount; + this.hurtResistantTime = this.maxHurtResistantTime; +- this.damageEntity(source, amount); + this.maxHurtTime = 10; + this.hurtTime = this.maxHurtTime; + } + ++ // CraftBukkit start ++ if (this instanceof EntityAnimal) { ++ ((EntityAnimal) this).resetInLove(); ++ if (this instanceof EntityTameable) { ++ if (((EntityTameable) this).getAISit() == null) ++ ((EntityTameable) this).setAISit(new EntityAISit((EntityTameable) this)); ++ ((EntityTameable) this).getAISit().setSitting(false); ++ } ++ } ++ // CraftBukkit end ++ + this.attackedAtYaw = 0.0F; + Entity entity1 = source.getTrueSource(); + +@@ -943,9 +1103,9 @@ + this.recentlyHit = 100; + this.attackingPlayer = (EntityPlayer)entity1; + } +- else if (entity1 instanceof net.minecraft.entity.passive.EntityTameable) ++ else if (entity1 instanceof EntityTameable) + { +- net.minecraft.entity.passive.EntityTameable entitywolf = (net.minecraft.entity.passive.EntityTameable)entity1; ++ EntityTameable entitywolf = (EntityTameable)entity1; + + if (entitywolf.isTamed()) + { +@@ -1065,22 +1225,30 @@ + else + { + ItemStack itemstack = null; ++ // CraftBukkit start ++ ItemStack itemstack1 = ItemStack.EMPTY; + + for (EnumHand enumhand : EnumHand.values()) + { +- ItemStack itemstack1 = this.getHeldItem(enumhand); ++ itemstack1 = this.getHeldItem(enumhand); + + if (itemstack1.getItem() == Items.TOTEM_OF_UNDYING) + { + itemstack = itemstack1.copy(); +- itemstack1.shrink(1); ++// itemstack1.shrink(1); // CraftBukkit + break; + } + } + +- if (itemstack != null) +- { +- if (this instanceof EntityPlayerMP) ++ EntityResurrectEvent event = new EntityResurrectEvent((LivingEntity) this.getBukkitEntity()); ++ event.setCancelled(itemstack == null); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ if (!itemstack1.isEmpty()) { ++ itemstack1.shrink(1); ++ } ++ if (itemstack != null && this instanceof EntityPlayerMP) + { + EntityPlayerMP entityplayermp = (EntityPlayerMP)this; + entityplayermp.addStat(StatList.getObjectUseStats(Items.TOTEM_OF_UNDYING)); +@@ -1094,7 +1262,8 @@ + this.world.setEntityState(this, (byte)35); + } + +- return itemstack != null; ++// return itemstack != null; ++ return !event.isCancelled(); + } + } + +@@ -1196,11 +1365,25 @@ + boolean flag = this.recentlyHit > 0; + this.dropLoot(flag, i, cause); + } +- ++ + captureDrops = false; + + if (!net.minecraftforge.common.ForgeHooks.onLivingDrops(this, cause, capturedDrops, i, recentlyHit > 0)) + { ++ // Kettle start - first allow forge to alter drops, then plugins and finally spawn items ++ if (capturedDrops.size() > 0) { ++ this.drops = new ArrayList<>(); ++ // Kettle - don't use forceDrops ++ for(EntityItem item: capturedDrops) ++ { ++ this.drops.add(CraftItemStack.asCraftMirror(item.getItem())); ++ } ++ CraftEventFactory.callEntityDeathEvent(this, this.drops); ++ } else { ++ CraftEventFactory.callEntityDeathEvent(this); ++ // Kettle end ++ } ++ + for (EntityItem item : capturedDrops) + { + world.spawnEntity(item); +@@ -1321,8 +1504,13 @@ + + if (i > 0) + { ++ // CraftBukkit start ++ if (!this.attackEntityFrom(DamageSource.FALL, (float) i)) { ++ return; ++ } ++ // CraftBukkit end + this.playSound(this.getFallSound(i), 1.0F, 1.0F); +- this.attackEntityFrom(DamageSource.FALL, (float)i); ++// this.attackEntityFrom(DamageSource.FALL, (float)i); // CraftBukkit - moved up + int j = MathHelper.floor(this.posX); + int k = MathHelper.floor(this.posY - 0.20000000298023224D); + int l = MathHelper.floor(this.posZ); +@@ -1362,7 +1550,7 @@ + { + if (!source.isUnblockable()) + { +- this.damageArmor(damage); ++// this.damageArmor(damage); // CraftBukkit - Moved into damageEntity0(DamageSource, float) + damage = CombatRules.getDamageAfterAbsorb(damage, (float)this.getTotalArmorValue(), (float)this.getEntityAttribute(SharedMonsterAttributes.ARMOR_TOUGHNESS).getAttributeValue()); + } + +@@ -1377,7 +1565,8 @@ + } + else + { +- if (this.isPotionActive(MobEffects.RESISTANCE) && source != DamageSource.OUT_OF_WORLD) ++ // CraftBukkit - Moved to damageEntity0(DamageSource, float) ++ if (false && this.isPotionActive(MobEffects.RESISTANCE) && source != DamageSource.OUT_OF_WORLD) + { + int i = (this.getActivePotionEffect(MobEffects.RESISTANCE).getAmplifier() + 1) * 5; + int j = 25 - i; +@@ -1405,6 +1594,8 @@ + + protected void damageEntity(DamageSource damageSrc, float damageAmount) + { ++ this.damageEntity_CB(damageSrc, damageAmount); ++ /* + if (!this.isEntityInvulnerable(damageSrc)) + { + damageAmount = net.minecraftforge.common.ForgeHooks.onLivingHurt(this, damageSrc, damageAmount); +@@ -1424,8 +1615,153 @@ + this.setAbsorptionAmount(this.getAbsorptionAmount() - damageAmount); + } + } ++ */ + } + ++ // CraftBukkit start ++ protected boolean damageEntity_CB(final DamageSource damagesource, float f) { // void -> boolean, add final ++ if (!this.isEntityInvulnerable(damagesource)) { ++ final boolean human = this instanceof EntityPlayer; ++ f = net.minecraftforge.common.ForgeHooks.onLivingHurt(this, damagesource, f); ++ if (f < 0) return true; ++ if (human) { ++ f = net.minecraftforge.common.ISpecialArmor.ArmorProperties.applyArmor(this, ((EntityPlayer)this).inventory.armorInventory, damagesource, f); ++ if (f <= 0) return false; ++ } ++ float originalDamage = f; ++ Function hardHat = new Function() { ++ @Override ++ public Double apply(Double f) { ++ if ((damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && !EntityLivingBase.this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()) { ++ return -(f - (f * 0.75F)); ++ ++ } ++ return -0.0; ++ } ++ }; ++ float hardHatModifier = hardHat.apply((double) f).floatValue(); ++ f += hardHatModifier; ++ ++ Function blocking = new Function() { ++ @Override ++ public Double apply(Double f) { ++ return -((EntityLivingBase.this.canBlockDamageSource(damagesource)) ? f : 0.0); ++ } ++ }; ++ float blockingModifier = blocking.apply((double) f).floatValue(); ++ f += blockingModifier; ++ ++ Function armor = new Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(f - EntityLivingBase.this.applyArmorCalculations(damagesource, f.floatValue())); ++ } ++ }; ++ float armorModifier = armor.apply((double) f).floatValue(); ++ f += armorModifier; ++ ++ Function resistance = new Function() { ++ @Override ++ public Double apply(Double f) { ++ if (!damagesource.isDamageAbsolute() && EntityLivingBase.this.isPotionActive(MobEffects.RESISTANCE) && damagesource != DamageSource.OUT_OF_WORLD) { ++ int i = (EntityLivingBase.this.getActivePotionEffect(MobEffects.RESISTANCE).getAmplifier() + 1) * 5; ++ int j = 25 - i; ++ float f1 = f.floatValue() * (float) j; ++ return -(f - (f1 / 25.0F)); ++ } ++ return -0.0; ++ } ++ }; ++ float resistanceModifier = resistance.apply((double) f).floatValue(); ++ f += resistanceModifier; ++ ++ Function magic = new Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(f - EntityLivingBase.this.applyPotionDamageCalculations(damagesource, f.floatValue())); ++ } ++ }; ++ float magicModifier = magic.apply((double) f).floatValue(); ++ f += magicModifier; ++ ++ Function absorption = new Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(Math.max(f - Math.max(f - EntityLivingBase.this.getAbsorptionAmount(), 0.0F), 0.0F)); ++ } ++ }; ++ float absorptionModifier = absorption.apply((double) f).floatValue(); ++ ++ EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ f = (float) event.getFinalDamage(); ++ ++ // Apply damage to helmet ++ if ((damagesource == DamageSource.ANVIL || damagesource == DamageSource.FALLING_BLOCK) && this.getItemStackFromSlot(EntityEquipmentSlot.HEAD) != null) { ++ this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).damageItem((int) (event.getDamage() * 4.0F + this.rand.nextFloat() * event.getDamage() * 2.0F), this); ++ } ++ ++ // Apply damage to armor ++ if (!damagesource.isUnblockable()) { ++ float armorDamage = (float) (event.getDamage() + event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING) + event.getDamage(EntityDamageEvent.DamageModifier.HARD_HAT)); ++ this.damageArmor(armorDamage); ++ } ++ ++ // Apply blocking code // PAIL: steal from above ++ if (event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING) < 0) { ++ this.damageShield((float) -event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING)); ++ Entity entity = damagesource.getImmediateSource(); ++ ++ if (entity instanceof EntityLivingBase) { ++ this.blockUsingShield((EntityLivingBase) entity); ++ } ++ } ++ ++ absorptionModifier = (float) -event.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION); ++ this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F)); ++ if (f > 0 || !human) { ++ if (human) { ++ // PAIL: Be sure to drag all this code from the EntityPlayer subclass each update. ++ ((EntityPlayer) this).addExhaustion(damagesource.getHungerDamage()); ++ if (f < 3.4028235E37F) { ++ ((EntityPlayer) this).addStat(StatList.DAMAGE_TAKEN, Math.round(f * 10.0F)); ++ } ++ } ++ // CraftBukkit end ++ float f2 = this.getHealth(); ++ ++ this.setHealth(f2 - f); ++ this.getCombatTracker().trackDamage(damagesource, f2, f); ++ // CraftBukkit start ++ if (!human) { ++ this.setAbsorptionAmount(this.getAbsorptionAmount() - f); ++ } ++ ++ return true; ++ } else { ++ // Duplicate triggers if blocking ++ if (event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING) < 0) { ++ if (this instanceof EntityPlayerMP) { ++ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((EntityPlayerMP) this, damagesource, f, originalDamage, true); ++ } ++ ++ if (damagesource.getTrueSource() instanceof EntityPlayerMP) { ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((EntityPlayerMP) damagesource.getTrueSource(), this, damagesource, f, originalDamage, true); ++ } ++ ++ return false; ++ } else { ++ return originalDamage > 0; ++ } ++ // CraftBukkit end ++ } ++ } ++ return false; // CraftBukkit ++ } ++ + public CombatTracker getCombatTracker() + { + return this._combatTracker; +@@ -1605,6 +1941,7 @@ + if (this.attributeMap == null) + { + this.attributeMap = new AttributeMap(); ++ this.craftAttributes = new CraftAttributeMap(attributeMap); // CraftBukkit + } + + return this.attributeMap; +@@ -1900,7 +2237,8 @@ + + if (this.onGround && !this.world.isRemote) + { +- this.setFlag(7, false); ++ if (getFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) ++ this.setFlag(7, false); + } + } + else +@@ -2345,7 +2683,6 @@ + } + + this.world.profiler.startSection("ai"); +- + if (this.isMovementBlocked()) + { + this.isJumping = false; +@@ -2426,6 +2763,7 @@ + + if (!this.world.isRemote) + { ++ if (flag != this.getFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, flag).isCancelled()) // CraftBukkit + this.setFlag(7, flag); + } + } +@@ -2560,12 +2898,12 @@ + + public boolean canBeCollidedWith() + { +- return !this.isDead; ++ return !this.isDead && this.collides; + } + + public boolean canBePushed() + { +- return this.isEntityAlive() && !this.isOnLadder(); ++ return this.isEntityAlive() && !this.isOnLadder() && this.collides; // CraftBukkit + } + + protected void markVelocityChanged() +@@ -2629,7 +2967,7 @@ + { + PotionEffect effect = iterator.next(); + +- if (effect.isCurativeItem(curativeItem) && !net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.living.PotionEvent.PotionRemoveEvent(this, effect))) ++ if (effect.isCurativeItem(curativeItem)) + { + onFinishedPotionEffect(effect); + iterator.remove(); +@@ -2667,7 +3005,8 @@ + if (this.isHandActive()) + { + ItemStack itemstack = this.getHeldItem(this.getActiveHand()); +- if (net.minecraftforge.common.ForgeHooks.canContinueUsing(this.activeItemStack, itemstack)) this.activeItemStack = itemstack; ++ if (net.minecraftforge.common.ForgeHooks.canContinueUsing(this.activeItemStack, itemstack)) ++ this.activeItemStack = itemstack; + + if (itemstack == this.activeItemStack) + { +@@ -2786,7 +3125,24 @@ + { + this.updateItemUse(this.activeItemStack, 16); + ItemStack activeItemStackCopy = this.activeItemStack.copy(); +- ItemStack itemstack = this.activeItemStack.onItemUseFinish(this.world, this); ++ // CraftBukkit start - fire PlayerItemConsumeEvent ++ ItemStack itemstack; ++ if (this instanceof EntityPlayer) { ++ org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.activeItemStack); ++ PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ // Update client ++ ((EntityPlayerMP) this).getBukkitEntity().updateInventory(); ++ ((EntityPlayerMP) this).getBukkitEntity().updateScaledHealth(); ++ return; ++ } ++ ++ itemstack = (craftItem.equals(event.getItem())) ? this.activeItemStack.onItemUseFinish(this.world, this) : CraftItemStack.asNMSCopy(event.getItem()).onItemUseFinish(world, this); ++ } else { ++ itemstack = this.activeItemStack.onItemUseFinish(this.world, this); ++ } + itemstack = net.minecraftforge.event.ForgeEventFactory.onItemUseFinish(this, activeItemStackCopy, getItemInUseCount(), itemstack); + this.setHeldItem(this.getActiveHand(), itemstack); + this.resetActiveHand(); +@@ -2897,12 +3253,17 @@ + + if (flag1) + { +- this.setPositionAndUpdate(this.posX, this.posY, this.posZ); +- +- if (world.getCollisionBoxes(this, this.getEntityBoundingBox()).isEmpty() && !world.containsAnyLiquid(this.getEntityBoundingBox())) +- { +- flag = true; ++ // CraftBukkit start - Teleport event ++ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.world.getWorld(), d0, d1, d2), new Location(this.world.getWorld(), this.posX, this.posY, this.posZ)); ++ this.world.getServer().getPluginManager().callEvent(teleport); ++ if (!teleport.isCancelled()) { ++ Location to = teleport.getTo(); ++ this.setPositionAndUpdate(to.getX(), to.getY(), to.getZ()); ++ if (world.getCollisionBoxes(this, this.getEntityBoundingBox()).isEmpty() && !world.containsAnyLiquid(this.getEntityBoundingBox())) { ++ flag = true; ++ } + } ++ // CraftBukkit end + } + } + +@@ -2936,6 +3297,11 @@ + } + } + ++ @Override ++ public float getBukkitYaw() { ++ return getRotationYawHead(); ++ } ++ + public boolean canBeHitWithPotion() + { + return true; +@@ -2949,7 +3315,7 @@ + @SuppressWarnings("unchecked") + @Override + @Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + if (capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + { +@@ -2961,7 +3327,7 @@ + } + + @Override +- public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + return capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY || super.hasCapability(capability, facing); + } diff --git a/patches/net/minecraft/entity/EntityTracker.java.patch b/patches/net/minecraft/entity/EntityTracker.java.patch new file mode 100644 index 00000000..f5156318 --- /dev/null +++ b/patches/net/minecraft/entity/EntityTracker.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityTracker.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityTracker.java +@@ -53,13 +53,13 @@ + private static final Logger LOGGER = LogManager.getLogger(); + private final WorldServer world; + private final Set entries = Sets.newHashSet(); +- private final IntHashMap trackedEntityHashTable = new IntHashMap(); ++ public final IntHashMap trackedEntityHashTable = new IntHashMap(); + private int maxTrackingDistanceThreshold; + + public EntityTracker(WorldServer theWorldIn) + { + this.world = theWorldIn; +- this.maxTrackingDistanceThreshold = theWorldIn.getMinecraftServer().getPlayerList().getEntityViewDistance(); ++ this.maxTrackingDistanceThreshold = net.minecraft.server.management.PlayerChunkMap.getFurthestViewableBlock(theWorldIn.spigotConfig.viewDistance); // Spigot + } + + public static long getPositionLong(double value) +@@ -77,6 +77,9 @@ + + public void track(Entity entityIn) + { ++ if (this.trackedEntityHashTable.containsItem(entityIn.getEntityId())){ ++ return; ++ } + if (net.minecraftforge.fml.common.registry.EntityRegistry.instance().tryTrackingEntity(this, entityIn)) return; + + if (entityIn instanceof EntityPlayerMP) +@@ -198,7 +201,7 @@ + } + else if (entityIn instanceof EntityAreaEffectCloud) + { +- this.track(entityIn, 160, Integer.MAX_VALUE, true); ++ this.track(entityIn, 160,10, true); // CraftBukkit + } + else if (entityIn instanceof EntityEnderCrystal) + { +@@ -217,11 +220,12 @@ + + public void track(Entity entityIn, int trackingRange, final int updateFrequency, boolean sendVelocityUpdates) + { ++ trackingRange = org.spigotmc.TrackingRange.getEntityTrackingRange(entityIn, trackingRange); // Spigot + try + { + if (this.trackedEntityHashTable.containsItem(entityIn.getEntityId())) + { +- throw new IllegalStateException("Entity is already tracked!"); ++ return; + } + + EntityTrackerEntry entitytrackerentry = new EntityTrackerEntry(entityIn, trackingRange, this.maxTrackingDistanceThreshold, updateFrequency, sendVelocityUpdates); diff --git a/patches/net/minecraft/entity/EntityTrackerEntry.java.patch b/patches/net/minecraft/entity/EntityTrackerEntry.java.patch new file mode 100644 index 00000000..a620b57b --- /dev/null +++ b/patches/net/minecraft/entity/EntityTrackerEntry.java.patch @@ -0,0 +1,282 @@ +--- ../src-base/minecraft/net/minecraft/entity/EntityTrackerEntry.java ++++ ../src-work/minecraft/net/minecraft/entity/EntityTrackerEntry.java +@@ -64,9 +64,14 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.storage.MapData; ++import net.minecraftforge.fml.common.FMLLog; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerVelocityEvent; + ++import javax.annotation.Nullable; ++ + public class EntityTrackerEntry + { + private static final Logger LOGGER = LogManager.getLogger(); +@@ -98,6 +103,7 @@ + + public EntityTrackerEntry(Entity entityIn, int rangeIn, int maxRangeIn, int updateFrequencyIn, boolean sendVelocityUpdatesIn) + { ++ entityIn.trackedEntity = this; // Paper + this.trackedEntity = entityIn; + this.range = rangeIn; + this.maxRange = maxRangeIn; +@@ -148,19 +154,19 @@ + if (!list.equals(this.passengers)) + { + this.passengers = list; +- this.sendPacketToTrackedPlayers(new SPacketSetPassengers(this.trackedEntity)); ++ this.sendToTrackingAndSelf(new SPacketSetPassengers(this.trackedEntity)); // CraftBukkit + } + +- if (this.trackedEntity instanceof EntityItemFrame && this.updateCounter % 10 == 0) ++ if (this.trackedEntity instanceof EntityItemFrame /*&& this.updateCounter % 10 == 0*/) // CraftBukkit - Moved below, should always enter this block + { + EntityItemFrame entityitemframe = (EntityItemFrame)this.trackedEntity; + ItemStack itemstack = entityitemframe.getDisplayedItem(); + +- if (itemstack.getItem() instanceof ItemMap) ++ if (this.updateCounter % 10 == 0 && itemstack.getItem() instanceof ItemMap)// CraftBukkit - Moved this.updateCounter % 10 logic here so item frames do not enter the other blocks + { + MapData mapdata = ((ItemMap) itemstack.getItem()).getMapData(itemstack, this.trackedEntity.world); + +- for (EntityPlayer entityplayer : players) ++ for (EntityPlayer entityplayer : trackingPlayers) + { + EntityPlayerMP entityplayermp = (EntityPlayerMP)entityplayer; + mapdata.updateVisiblePlayers(entityplayermp, itemstack); +@@ -212,6 +218,21 @@ + boolean flag = j * j + k * k + l * l >= 128L || this.updateCounter % 60 == 0; + boolean flag1 = Math.abs(k2 - this.encodedRotationYaw) >= 1 || Math.abs(i - this.encodedRotationPitch) >= 1; + ++ // CraftBukkit start - Code moved from below ++ if (flag) ++ { ++ this.encodedPosX = i1; ++ this.encodedPosY = i2; ++ this.encodedPosZ = j2; ++ } ++ ++ if (flag1) ++ { ++ this.encodedRotationYaw = k2; ++ this.encodedRotationPitch = i; ++ } ++ // CraftBukkit end ++ + if (this.updateCounter > 0 || this.trackedEntity instanceof EntityArrow) + { + if (j >= -32768L && j < 32768L && k >= -32768L && k < 32768L && l >= -32768L && l < 32768L && this.ticksSinceLastForcedTeleport <= 400 && !this.ridingEntity && this.onGround == this.trackedEntity.onGround) +@@ -236,6 +257,11 @@ + { + this.onGround = this.trackedEntity.onGround; + this.ticksSinceLastForcedTeleport = 0; ++ // CraftBukkit start - Refresh list of who can see a player before sending teleport packet ++ if (this.trackedEntity instanceof EntityPlayer) { ++ this.updatePlayerEntities(new java.util.ArrayList<>(this.trackingPlayers)); ++ } ++ // CraftBukkit end + this.resetPlayerVisibility(); + packet1 = new SPacketEntityTeleport(this.trackedEntity); + } +@@ -272,6 +298,7 @@ + + this.sendMetadata(); + ++ /* CraftBukkit start - Code moved up + if (flag) + { + this.encodedPosX = i1; +@@ -284,6 +311,7 @@ + this.encodedRotationYaw = k2; + this.encodedRotationPitch = i; + } ++ // CraftBukkit end */ + + this.ridingEntity = false; + } +@@ -303,7 +331,28 @@ + + if (this.trackedEntity.velocityChanged) + { +- this.sendToTrackingAndSelf(new SPacketEntityVelocity(this.trackedEntity)); ++ // this.sendToTrackingAndSelf(new SPacketEntityVelocity(this.trackedEntity)); ++ // CraftBukkit start - Create PlayerVelocity event ++ boolean cancelled = false; ++ ++ if (this.trackedEntity instanceof EntityPlayer) { ++ Player player = (Player) this.trackedEntity.getBukkitEntity(); ++ org.bukkit.util.Vector velocity = player.getVelocity(); ++ ++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone()); ++ this.trackedEntity.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ cancelled = true; ++ } else if (!velocity.equals(event.getVelocity())) { ++ player.setVelocity(event.getVelocity()); ++ } ++ } ++ ++ if (!cancelled) { ++ this.sendToTrackingAndSelf(new SPacketEntityVelocity(this.trackedEntity)); ++ } ++ // CraftBukkit end + this.trackedEntity.velocityChanged = false; + } + } +@@ -324,6 +373,11 @@ + + if (!set.isEmpty()) + { ++ // CraftBukkit start - Send scaled max health ++ if (this.trackedEntity instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) this.trackedEntity).getBukkitEntity().injectScaledMaxHealth(set, false); ++ } ++ // CraftBukkit end + this.sendToTrackingAndSelf(new SPacketEntityProperties(this.trackedEntity.getEntityId(), set)); + } + +@@ -376,6 +430,16 @@ + { + if (!this.trackingPlayers.contains(playerMP) && (this.isPlayerWatchingThisChunk(playerMP) || this.trackedEntity.forceSpawn)) + { ++ // CraftBukkit start - respect vanish API ++ if (this.trackedEntity instanceof EntityPlayerMP) { ++ Player player = ((EntityPlayerMP) this.trackedEntity).getBukkitEntity(); ++ if (!playerMP.getBukkitEntity().canSee(player)) { ++ return; ++ } ++ } ++ ++ playerMP.entityRemoveQueue.remove(Integer.valueOf(this.trackedEntity.getEntityId())); ++ // CraftBukkit end + this.trackingPlayers.add(playerMP); + Packet packet = this.createSpawnPacket(); + playerMP.connection.sendPacket(packet); +@@ -392,6 +456,13 @@ + AttributeMap attributemap = (AttributeMap)((EntityLivingBase)this.trackedEntity).getAttributeMap(); + Collection collection = attributemap.getWatchedAttributes(); + ++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health ++ if (this.trackedEntity.getEntityId() == playerMP.getEntityId()) { ++ // TODO: Maybe we should check for instanceof before casting to EntityPlayerMP? ++ ((EntityPlayerMP) this.trackedEntity).getBukkitEntity().injectScaledMaxHealth(collection, false); ++ } ++ // CraftBukkit end ++ + if (!collection.isEmpty()) + { + playerMP.connection.sendPacket(new SPacketEntityProperties(this.trackedEntity.getEntityId(), collection)); +@@ -435,6 +506,11 @@ + } + } + ++ // CraftBukkit start - Fix for nonsensical head yaw ++ this.lastHeadMotion = MathHelper.floor(this.trackedEntity.getRotationYawHead() * 256.0F / 360.0F); ++ this.sendPacketToTrackedPlayers(new SPacketEntityHeadLook(this.trackedEntity, (byte) lastHeadMotion)); ++ // CraftBukkit end ++ + if (this.trackedEntity instanceof EntityLivingBase) + { + EntityLivingBase entitylivingbase = (EntityLivingBase)this.trackedEntity; +@@ -457,6 +533,7 @@ + + this.trackedEntity.addTrackingPlayer(playerMP); + playerMP.addEntity(this.trackedEntity); ++ updatePassengers(playerMP); // Paper + net.minecraftforge.event.ForgeEventFactory.onStartEntityTracking(trackedEntity, playerMP); + } + } +@@ -465,6 +542,7 @@ + this.trackingPlayers.remove(playerMP); + this.trackedEntity.removeTrackingPlayer(playerMP); + playerMP.removeEntity(this.trackedEntity); ++ updatePassengers(playerMP); // Paper + net.minecraftforge.event.ForgeEventFactory.onStopEntityTracking(trackedEntity, playerMP); + } + } +@@ -472,6 +550,46 @@ + + public boolean isVisibleTo(EntityPlayerMP playerMP) + { ++ // Paper start ++ if (trackedEntity.isRiding()) { ++ return isTrackedBy(trackedEntity.getLowestRidingEntity(), playerMP); ++ } else if (hasPassengerInRange(trackedEntity, playerMP)) { ++ return true; ++ } ++ ++ return isInRangeOfPlayer(playerMP); ++ } ++ private static boolean hasPassengerInRange(Entity entity, EntityPlayerMP entityplayer) { ++ if (!entity.isBeingRidden()) { ++ return false; ++ } ++ for (Entity passenger : entity.riddenByEntities) { ++ if (passenger.trackedEntity != null && passenger.trackedEntity.isInRangeOfPlayer(entityplayer)) { ++ return true; ++ } ++ if (passenger.isBeingRidden()) { ++ if (hasPassengerInRange(passenger, entityplayer)) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ private static boolean isTrackedBy(Entity entity, EntityPlayerMP entityplayer) { ++ return entity == entityplayer || entity.trackedEntity != null && entity.trackedEntity.trackingPlayers.contains(entityplayer); ++ } ++ private void updatePassengers(EntityPlayerMP player) { ++ if (trackedEntity.isBeingRidden()) { ++ trackedEntity.riddenByEntities.forEach((e) -> { ++ if (e.trackedEntity != null) { ++ e.trackedEntity.updatePlayerEntity(player); ++ } ++ }); ++ player.connection.sendPacket(new SPacketSetPassengers(this.trackedEntity)); ++ } ++ } ++ private boolean isInRangeOfPlayer(EntityPlayerMP playerMP) { ++ // Paper end + double d0 = playerMP.posX - (double)this.encodedPosX / 4096.0D; + double d1 = playerMP.posZ - (double)this.encodedPosZ / 4096.0D; + int i = Math.min(this.range, this.maxRange); +@@ -491,11 +609,15 @@ + } + } + ++ @Nullable + private Packet createSpawnPacket() + { + if (this.trackedEntity.isDead) + { +- LOGGER.warn("Fetching addPacket for removed entity"); ++ // CraftBukkit start - Remove useless error spam, just return ++// LOGGER.warn("Fetching addPacket for removed entity"); ++ return null; ++ // CraftBukkit end + } + + Packet pkt = net.minecraftforge.fml.common.network.internal.FMLNetworkHandler.getEntitySpawningPacket(this.trackedEntity); +@@ -656,7 +778,8 @@ + } + else + { +- throw new IllegalArgumentException("Don't know how to add " + this.trackedEntity.getClass() + "!"); ++ FMLLog.log.warn(new IllegalArgumentException("Don't know how to add " + this.trackedEntity.getClass() + "!").getMessage() + "x:{}, y:{}x z:{}", this.trackedEntity.posX, this.trackedEntity.posY, this.trackedEntity.posZ); ++ return null; + } + } + +@@ -667,6 +790,7 @@ + this.trackingPlayers.remove(playerMP); + this.trackedEntity.removeTrackingPlayer(playerMP); + playerMP.removeEntity(this.trackedEntity); ++ updatePassengers(playerMP); // Paper + } + } + diff --git a/patches/net/minecraft/entity/SharedMonsterAttributes.java.patch b/patches/net/minecraft/entity/SharedMonsterAttributes.java.patch new file mode 100644 index 00000000..3724f8a1 --- /dev/null +++ b/patches/net/minecraft/entity/SharedMonsterAttributes.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/SharedMonsterAttributes.java ++++ ../src-work/minecraft/net/minecraft/entity/SharedMonsterAttributes.java +@@ -16,7 +16,8 @@ + public class SharedMonsterAttributes + { + private static final Logger LOGGER = LogManager.getLogger(); +- public static final IAttribute MAX_HEALTH = (new RangedAttribute((IAttribute)null, "generic.maxHealth", 20.0D, Float.MIN_VALUE, 1024.0D)).setDescription("Max Health").setShouldWatch(true); // Forge: set smallest max-health value to fix MC-119183. This gets rounded to float so we use the smallest positive float value. ++ // Spigot start ++ public static final IAttribute MAX_HEALTH = (new RangedAttribute((IAttribute)null, "generic.maxHealth", 20.0D, Float.MIN_VALUE, org.spigotmc.SpigotConfig.maxHealth)).setDescription("Max Health").setShouldWatch(true); // Forge: set smallest max-health value to fix MC-119183. This gets rounded to float so we use the smallest positive float value. + public static final IAttribute FOLLOW_RANGE = (new RangedAttribute((IAttribute)null, "generic.followRange", 32.0D, 0.0D, 2048.0D)).setDescription("Follow Range"); + public static final IAttribute KNOCKBACK_RESISTANCE = (new RangedAttribute((IAttribute)null, "generic.knockbackResistance", 0.0D, 0.0D, 1.0D)).setDescription("Knockback Resistance"); + public static final IAttribute MOVEMENT_SPEED = (new RangedAttribute((IAttribute)null, "generic.movementSpeed", 0.699999988079071D, 0.0D, 1024.0D)).setDescription("Movement Speed").setShouldWatch(true); +@@ -26,6 +27,7 @@ + public static final IAttribute ARMOR = (new RangedAttribute((IAttribute)null, "generic.armor", 0.0D, 0.0D, 30.0D)).setShouldWatch(true); + public static final IAttribute ARMOR_TOUGHNESS = (new RangedAttribute((IAttribute)null, "generic.armorToughness", 0.0D, 0.0D, 20.0D)).setShouldWatch(true); + public static final IAttribute LUCK = (new RangedAttribute((IAttribute)null, "generic.luck", 0.0D, -1024.0D, 1024.0D)).setShouldWatch(true); ++ // Spigot end + + public static NBTTagList writeBaseAttributeMapToNBT(AbstractAttributeMap map) + { diff --git a/patches/net/minecraft/entity/ai/EntityAIBreakDoor.java.patch b/patches/net/minecraft/entity/ai/EntityAIBreakDoor.java.patch new file mode 100644 index 00000000..f1addcc3 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIBreakDoor.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIBreakDoor.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIBreakDoor.java +@@ -84,6 +84,10 @@ + + if (this.breakingTime == 240 && this.entity.world.getDifficulty() == EnumDifficulty.HARD) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.entity, this.doorPosition.getX(), this.doorPosition.getY(), this.doorPosition.getZ()).isCancelled()) { ++ this.startExecuting(); ++ return; ++ } + this.entity.world.setBlockToAir(this.doorPosition); + this.entity.world.playEvent(1021, this.doorPosition, 0); + this.entity.world.playEvent(2001, this.doorPosition, Block.getIdFromBlock(this.doorBlock)); diff --git a/patches/net/minecraft/entity/ai/EntityAIDefendVillage.java.patch b/patches/net/minecraft/entity/ai/EntityAIDefendVillage.java.patch new file mode 100644 index 00000000..170e6ff2 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIDefendVillage.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIDefendVillage.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIDefendVillage.java +@@ -51,7 +51,7 @@ + + public void startExecuting() + { +- this.irongolem.setAttackTarget(this.villageAgressorTarget); ++ this.irongolem.setAttackTarget(this.villageAgressorTarget, org.bukkit.event.entity.EntityTargetEvent.TargetReason.DEFEND_VILLAGE, true); + super.startExecuting(); + } + } diff --git a/patches/net/minecraft/entity/ai/EntityAIEatGrass.java.patch b/patches/net/minecraft/entity/ai/EntityAIEatGrass.java.patch new file mode 100644 index 00000000..38e50194 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIEatGrass.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIEatGrass.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIEatGrass.java +@@ -10,6 +10,8 @@ + import net.minecraft.init.Blocks; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.Material; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityAIEatGrass extends EntityAIBase + { +@@ -78,7 +80,7 @@ + + if (IS_TALL_GRASS.apply(this.entityWorld.getBlockState(blockpos))) + { +- if (net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.entityWorld, this.grassEaterEntity)) ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.grassEaterEntity, this.grassEaterEntity.world.getWorld().getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()), Material.AIR, !net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.entityWorld, this.grassEaterEntity)).isCancelled()) + { + this.entityWorld.destroyBlock(blockpos, false); + } +@@ -91,7 +93,7 @@ + + if (this.entityWorld.getBlockState(blockpos1).getBlock() == Blocks.GRASS) + { +- if (net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.entityWorld, this.grassEaterEntity)) ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.grassEaterEntity, this.grassEaterEntity.world.getWorld().getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()), Material.AIR, !net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.entityWorld, this.grassEaterEntity)).isCancelled()) + { + this.entityWorld.playEvent(2001, blockpos1, Block.getIdFromBlock(Blocks.GRASS)); + this.entityWorld.setBlockState(blockpos1, Blocks.DIRT.getDefaultState(), 2); diff --git a/patches/net/minecraft/entity/ai/EntityAIFindEntityNearest.java.patch b/patches/net/minecraft/entity/ai/EntityAIFindEntityNearest.java.patch new file mode 100644 index 00000000..7dfdafab --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIFindEntityNearest.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIFindEntityNearest.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIFindEntityNearest.java +@@ -102,7 +102,7 @@ + + public void startExecuting() + { +- this.mob.setAttackTarget(this.target); ++ this.mob.setAttackTarget(this.target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); + super.startExecuting(); + } + diff --git a/patches/net/minecraft/entity/ai/EntityAIFindEntityNearestPlayer.java.patch b/patches/net/minecraft/entity/ai/EntityAIFindEntityNearestPlayer.java.patch new file mode 100644 index 00000000..a265d7e2 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIFindEntityNearestPlayer.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIFindEntityNearestPlayer.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIFindEntityNearestPlayer.java +@@ -133,7 +133,7 @@ + + public void startExecuting() + { +- this.entityLiving.setAttackTarget(this.entityTarget); ++ this.entityLiving.setAttackTarget(this.entityTarget, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); + super.startExecuting(); + } + diff --git a/patches/net/minecraft/entity/ai/EntityAIFollowOwner.java.patch b/patches/net/minecraft/entity/ai/EntityAIFollowOwner.java.patch new file mode 100644 index 00000000..b7313df3 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIFollowOwner.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIFollowOwner.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIFollowOwner.java +@@ -13,6 +13,9 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.event.entity.EntityTeleportEvent; + + public class EntityAIFollowOwner extends EntityAIBase + { +@@ -114,7 +117,17 @@ + { + if ((l < 1 || i1 < 1 || l > 3 || i1 > 3) && this.isTeleportFriendlyBlock(i, j, k, l, i1)) + { +- this.tameable.setLocationAndAngles((double)((float)(i + l) + 0.5F), (double)k, (double)((float)(j + i1) + 0.5F), this.tameable.rotationYaw, this.tameable.rotationPitch); ++ // this.tameable.setLocationAndAngles((double)((float)(i + l) + 0.5F), (double)k, (double)((float)(j + i1) + 0.5F), this.tameable.rotationYaw, this.tameable.rotationPitch); ++ CraftEntity entity = this.tameable.getBukkitEntity(); ++ Location to = new Location(entity.getWorld(), (double) ((float) (i + l) + 0.5F), (double) k, (double) ((float) (j + i1) + 0.5F), this.tameable.rotationYaw, this.tameable.rotationPitch); ++ EntityTeleportEvent event = new EntityTeleportEvent(entity, entity.getLocation(), to); ++ this.tameable.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ to = event.getTo(); ++ ++ this.tameable.setLocationAndAngles(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); + this.petPathfinder.clearPath(); + return; + } diff --git a/patches/net/minecraft/entity/ai/EntityAIHarvestFarmland.java.patch b/patches/net/minecraft/entity/ai/EntityAIHarvestFarmland.java.patch new file mode 100644 index 00000000..b4d902b2 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIHarvestFarmland.java.patch @@ -0,0 +1,64 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIHarvestFarmland.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIHarvestFarmland.java +@@ -61,7 +61,9 @@ + + if (this.currentTask == 0 && block instanceof BlockCrops && ((BlockCrops)block).isMaxAge(iblockstate)) + { +- world.destroyBlock(blockpos, true); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.villager, blockpos, Blocks.AIR, 0).isCancelled()) { ++ world.destroyBlock(blockpos, true); ++ } + } + else if (this.currentTask == 1 && iblockstate.getMaterial() == Material.AIR) + { +@@ -74,32 +76,42 @@ + + if (!itemstack.isEmpty()) + { ++ Block planted = null; + if (itemstack.getItem() == Items.WHEAT_SEEDS) + { +- world.setBlockState(blockpos, Blocks.WHEAT.getDefaultState(), 3); ++ // world.setBlockState(blockpos, Blocks.WHEAT.getDefaultState(), 3); ++ planted = Blocks.WHEAT; + flag = true; + } + else if (itemstack.getItem() == Items.POTATO) + { +- world.setBlockState(blockpos, Blocks.POTATOES.getDefaultState(), 3); ++ // world.setBlockState(blockpos, Blocks.POTATOES.getDefaultState(), 3); ++ planted = Blocks.POTATOES; + flag = true; + } + else if (itemstack.getItem() == Items.CARROT) + { +- world.setBlockState(blockpos, Blocks.CARROTS.getDefaultState(), 3); ++ // world.setBlockState(blockpos, Blocks.CARROTS.getDefaultState(), 3); ++ planted = Blocks.CARROTS; + flag = true; + } + else if (itemstack.getItem() == Items.BEETROOT_SEEDS) + { +- world.setBlockState(blockpos, Blocks.BEETROOTS.getDefaultState(), 3); ++ // world.setBlockState(blockpos, Blocks.BEETROOTS.getDefaultState(), 3); ++ planted = Blocks.BEETROOTS; + flag = true; +- } +- else if (itemstack.getItem() instanceof net.minecraftforge.common.IPlantable) { +- if(((net.minecraftforge.common.IPlantable)itemstack.getItem()).getPlantType(world,blockpos) == net.minecraftforge.common.EnumPlantType.Crop) { +- world.setBlockState(blockpos, ((net.minecraftforge.common.IPlantable)itemstack.getItem()).getPlant(world,blockpos),3); ++ } else if (itemstack.getItem() instanceof net.minecraftforge.common.IPlantable) { ++ if (((net.minecraftforge.common.IPlantable) itemstack.getItem()).getPlantType(world, blockpos) == net.minecraftforge.common.EnumPlantType.Crop) { ++ // world.setBlockState(blockpos, ((net.minecraftforge.common.IPlantable) itemstack.getItem()).getPlant(world, blockpos), 3); ++ planted = ((net.minecraftforge.common.IPlantable) itemstack.getItem()).getPlant(world, blockpos).getBlock(); + flag = true; + } + } ++ if (planted != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.villager, blockpos, planted, 0).isCancelled()) { ++ world.setBlockState(blockpos, planted.getDefaultState(), 3); ++ } else { ++ flag = false; ++ } + } + + if (flag) diff --git a/patches/net/minecraft/entity/ai/EntityAIHurtByTarget.java.patch b/patches/net/minecraft/entity/ai/EntityAIHurtByTarget.java.patch new file mode 100644 index 00000000..d97d6fcc --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIHurtByTarget.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIHurtByTarget.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIHurtByTarget.java +@@ -28,7 +28,7 @@ + + public void startExecuting() + { +- this.taskOwner.setAttackTarget(this.taskOwner.getRevengeTarget()); ++ this.taskOwner.setAttackTarget(this.taskOwner.getRevengeTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); + this.target = this.taskOwner.getAttackTarget(); + this.revengeTimerOld = this.taskOwner.getRevengeTimer(); + this.unseenMemoryTicks = 300; +@@ -70,6 +70,6 @@ + + protected void setEntityAttackTarget(EntityCreature creatureIn, EntityLivingBase entityLivingBaseIn) + { +- creatureIn.setAttackTarget(entityLivingBaseIn); ++ creatureIn.setAttackTarget(entityLivingBaseIn, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); + } + } diff --git a/patches/net/minecraft/entity/ai/EntityAIMate.java.patch b/patches/net/minecraft/entity/ai/EntityAIMate.java.patch new file mode 100644 index 00000000..e61a96d4 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIMate.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIMate.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIMate.java +@@ -6,6 +6,7 @@ + import net.minecraft.entity.EntityAgeable; + import net.minecraft.entity.item.EntityXPOrb; + import net.minecraft.entity.passive.EntityAnimal; ++import net.minecraft.entity.passive.EntityTameable; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.stats.StatList; + import net.minecraft.util.EnumParticleTypes; +@@ -106,6 +107,9 @@ + + if (entityageable != null) + { ++ if (entityageable instanceof EntityTameable && ((EntityTameable) entityageable).isTamed()) { ++ entityageable.persistenceRequired = true; ++ } + EntityPlayerMP entityplayermp = this.animal.getLoveCause(); + + if (entityplayermp == null && this.targetMate.getLoveCause() != null) +@@ -113,6 +117,13 @@ + entityplayermp = this.targetMate.getLoveCause(); + } + ++ int experience = this.animal.getRNG().nextInt(7) + 1; ++ org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, animal, targetMate, entityplayermp, this.animal.breedItem, experience); ++ if (entityBreedEvent.isCancelled()) { ++ return; ++ } ++ experience = entityBreedEvent.getExperience(); ++ + if (entityplayermp != null) + { + entityplayermp.addStat(StatList.ANIMALS_BRED); +@@ -125,7 +136,7 @@ + this.targetMate.resetInLove(); + entityageable.setGrowingAge(-24000); + entityageable.setLocationAndAngles(this.animal.posX, this.animal.posY, this.animal.posZ, 0.0F, 0.0F); +- this.world.spawnEntity(entityageable); ++ this.world.spawnEntity(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); + Random random = this.animal.getRNG(); + + for (int i = 0; i < 7; ++i) +@@ -141,7 +152,8 @@ + + if (this.world.getGameRules().getBoolean("doMobLoot")) + { +- this.world.spawnEntity(new EntityXPOrb(this.world, this.animal.posX, this.animal.posY, this.animal.posZ, random.nextInt(7) + 1)); ++ if (experience > 0) ++ this.world.spawnEntity(new EntityXPOrb(this.world, this.animal.posX, this.animal.posY, this.animal.posZ, experience)); + } + } + } diff --git a/patches/net/minecraft/entity/ai/EntityAINearestAttackableTarget.java.patch b/patches/net/minecraft/entity/ai/EntityAINearestAttackableTarget.java.patch new file mode 100644 index 00000000..2c295492 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAINearestAttackableTarget.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAINearestAttackableTarget.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAINearestAttackableTarget.java +@@ -122,7 +122,7 @@ + + public void startExecuting() + { +- this.taskOwner.setAttackTarget(this.targetEntity); ++ this.taskOwner.setAttackTarget(this.targetEntity, targetEntity instanceof EntityPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); + super.startExecuting(); + } + diff --git a/patches/net/minecraft/entity/ai/EntityAIOwnerHurtByTarget.java.patch b/patches/net/minecraft/entity/ai/EntityAIOwnerHurtByTarget.java.patch new file mode 100644 index 00000000..f7edd6d4 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIOwnerHurtByTarget.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIOwnerHurtByTarget.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIOwnerHurtByTarget.java +@@ -41,7 +41,7 @@ + + public void startExecuting() + { +- this.taskOwner.setAttackTarget(this.attacker); ++ this.taskOwner.setAttackTarget(this.attacker, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); + EntityLivingBase entitylivingbase = this.tameable.getOwner(); + + if (entitylivingbase != null) diff --git a/patches/net/minecraft/entity/ai/EntityAIOwnerHurtTarget.java.patch b/patches/net/minecraft/entity/ai/EntityAIOwnerHurtTarget.java.patch new file mode 100644 index 00000000..1238e343 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIOwnerHurtTarget.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIOwnerHurtTarget.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIOwnerHurtTarget.java +@@ -41,7 +41,7 @@ + + public void startExecuting() + { +- this.taskOwner.setAttackTarget(this.attacker); ++ this.taskOwner.setAttackTarget(this.attacker, org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); + EntityLivingBase entitylivingbase = this.tameable.getOwner(); + + if (entitylivingbase != null) diff --git a/patches/net/minecraft/entity/ai/EntityAIPanic.java.patch b/patches/net/minecraft/entity/ai/EntityAIPanic.java.patch new file mode 100644 index 00000000..a8996cc4 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIPanic.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIPanic.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIPanic.java +@@ -73,6 +73,12 @@ + + public boolean shouldContinueExecuting() + { ++ // CraftBukkit start - introduce a temporary timeout hack until this is fixed properly ++ if ((this.creature.ticksExisted - this.creature.revengeTimer) > 100) { ++ this.creature.onKillEntity(null); ++ return false; ++ } ++ // CraftBukkit end + return !this.creature.getNavigator().noPath(); + } + diff --git a/patches/net/minecraft/entity/ai/EntityAIRunAroundLikeCrazy.java.patch b/patches/net/minecraft/entity/ai/EntityAIRunAroundLikeCrazy.java.patch new file mode 100644 index 00000000..d61daadb --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIRunAroundLikeCrazy.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIRunAroundLikeCrazy.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIRunAroundLikeCrazy.java +@@ -70,7 +70,7 @@ + int i = this.horseHost.getTemper(); + int j = this.horseHost.getMaxTemper(); + +- if (j > 0 && this.horseHost.getRNG().nextInt(j) < i && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(horseHost, (EntityPlayer)entity)) ++ if (j > 0 && this.horseHost.getRNG().nextInt(j) < i && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(horseHost, (EntityPlayer)entity) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horseHost, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horseHost.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) + { + this.horseHost.setTamedBy((EntityPlayer)entity); + return; diff --git a/patches/net/minecraft/entity/ai/EntityAISit.java.patch b/patches/net/minecraft/entity/ai/EntityAISit.java.patch new file mode 100644 index 00000000..eb474d33 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAISit.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAISit.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAISit.java +@@ -18,7 +18,8 @@ + { + if (!this.tameable.isTamed()) + { +- return false; ++ // return false; ++ return this.isSitting && this.tameable.getAttackTarget() == null; // CraftBukkit - Allow sitting for wild animals + } + else if (this.tameable.isInWater()) + { diff --git a/patches/net/minecraft/entity/ai/EntityAISkeletonRiders.java.patch b/patches/net/minecraft/entity/ai/EntityAISkeletonRiders.java.patch new file mode 100644 index 00000000..27f0f65e --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAISkeletonRiders.java.patch @@ -0,0 +1,55 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAISkeletonRiders.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAISkeletonRiders.java +@@ -12,6 +12,8 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.DifficultyInstance; + ++import javax.annotation.Nullable; ++ + public class EntityAISkeletonRiders extends EntityAIBase + { + private final EntitySkeletonHorse horse; +@@ -34,17 +36,19 @@ + this.horse.setGrowingAge(0); + this.horse.world.addWeatherEffect(new EntityLightningBolt(this.horse.world, this.horse.posX, this.horse.posY, this.horse.posZ, true)); + EntitySkeleton entityskeleton = this.createSkeleton(difficultyinstance, this.horse); +- entityskeleton.startRiding(this.horse); ++ if (entityskeleton != null) entityskeleton.startRiding(this.horse); + + for (int i = 0; i < 3; ++i) + { + AbstractHorse abstracthorse = this.createHorse(difficultyinstance); ++ if (abstracthorse == null) continue; // CraftBukkit + EntitySkeleton entityskeleton1 = this.createSkeleton(difficultyinstance, abstracthorse); +- entityskeleton1.startRiding(abstracthorse); ++ if (entityskeleton1 != null) entityskeleton1.startRiding(abstracthorse); + abstracthorse.addVelocity(this.horse.getRNG().nextGaussian() * 0.5D, 0.0D, this.horse.getRNG().nextGaussian() * 0.5D); + } + } + ++ @Nullable + private AbstractHorse createHorse(DifficultyInstance p_188515_1_) + { + EntitySkeletonHorse entityskeletonhorse = new EntitySkeletonHorse(this.horse.world); +@@ -54,10 +58,11 @@ + entityskeletonhorse.enablePersistence(); + entityskeletonhorse.setHorseTamed(true); + entityskeletonhorse.setGrowingAge(0); +- entityskeletonhorse.world.spawnEntity(entityskeletonhorse); ++ if (!entityskeletonhorse.world.spawnEntity(entityskeletonhorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRAP)) return null; // CraftBukkit + return entityskeletonhorse; + } + ++ @Nullable + private EntitySkeleton createSkeleton(DifficultyInstance p_188514_1_, AbstractHorse p_188514_2_) + { + EntitySkeleton entityskeleton = new EntitySkeleton(p_188514_2_.world); +@@ -73,7 +78,7 @@ + + entityskeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, EnchantmentHelper.addRandomEnchantment(entityskeleton.getRNG(), entityskeleton.getHeldItemMainhand(), (int)(5.0F + p_188514_1_.getClampedAdditionalDifficulty() * (float)entityskeleton.getRNG().nextInt(18)), false)); + entityskeleton.setItemStackToSlot(EntityEquipmentSlot.HEAD, EnchantmentHelper.addRandomEnchantment(entityskeleton.getRNG(), entityskeleton.getItemStackFromSlot(EntityEquipmentSlot.HEAD), (int)(5.0F + p_188514_1_.getClampedAdditionalDifficulty() * (float)entityskeleton.getRNG().nextInt(18)), false)); +- entityskeleton.world.spawnEntity(entityskeleton); ++ if (!entityskeleton.world.spawnEntity(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY)) return null; // CraftBukkit + return entityskeleton; + } + } diff --git a/patches/net/minecraft/entity/ai/EntityAITarget.java.patch b/patches/net/minecraft/entity/ai/EntityAITarget.java.patch new file mode 100644 index 00000000..84603d4a --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAITarget.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAITarget.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAITarget.java +@@ -13,6 +13,7 @@ + import net.minecraft.scoreboard.Team; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; ++import org.bukkit.event.entity.EntityTargetEvent; + + public abstract class EntityAITarget extends EntityAIBase + { +@@ -92,7 +93,7 @@ + } + else + { +- this.taskOwner.setAttackTarget(entitylivingbase); ++ this.taskOwner.setAttackTarget(entitylivingbase, EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); + return true; + } + } +@@ -115,7 +116,7 @@ + + public void resetTask() + { +- this.taskOwner.setAttackTarget((EntityLivingBase)null); ++ this.taskOwner.setAttackTarget((EntityLivingBase)null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); + this.target = null; + } + diff --git a/patches/net/minecraft/entity/ai/EntityAITempt.java.patch b/patches/net/minecraft/entity/ai/EntityAITempt.java.patch new file mode 100644 index 00000000..29d51c7c --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAITempt.java.patch @@ -0,0 +1,52 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAITempt.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAITempt.java +@@ -3,10 +3,14 @@ + import com.google.common.collect.Sets; + import java.util.Set; + import net.minecraft.entity.EntityCreature; +-import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.EntityLivingBase; + import net.minecraft.item.Item; + import net.minecraft.item.ItemStack; + import net.minecraft.pathfinding.PathNavigateGround; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; + + public class EntityAITempt extends EntityAIBase + { +@@ -17,7 +21,7 @@ + private double targetZ; + private double pitch; + private double yaw; +- private EntityPlayer temptingPlayer; ++ private EntityLivingBase temptingPlayer; + private int delayTemptCounter; + private boolean isRunning; + private final Set temptItem; +@@ -53,6 +57,7 @@ + { + this.temptingPlayer = this.temptedEntity.world.getClosestPlayerToEntity(this.temptedEntity, 10.0D); + ++ /* + if (this.temptingPlayer == null) + { + return false; +@@ -61,6 +66,16 @@ + { + return this.isTempting(this.temptingPlayer.getHeldItemMainhand()) || this.isTempting(this.temptingPlayer.getHeldItemOffhand()); + } ++ */ ++ boolean tempt = this.temptingPlayer == null ? false : this.isTempting(this.temptingPlayer.getHeldItemMainhand()) || this.isTempting(this.temptingPlayer.getHeldItemOffhand()); ++ if (tempt) { ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this.temptedEntity, this.temptingPlayer, EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.temptingPlayer = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ return tempt; + } + } + diff --git a/patches/net/minecraft/entity/ai/EntityAIVillagerMate.java.patch b/patches/net/minecraft/entity/ai/EntityAIVillagerMate.java.patch new file mode 100644 index 00000000..d988b248 --- /dev/null +++ b/patches/net/minecraft/entity/ai/EntityAIVillagerMate.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/EntityAIVillagerMate.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/EntityAIVillagerMate.java +@@ -114,6 +114,9 @@ + private void giveBirth() + { + net.minecraft.entity.EntityAgeable entityvillager = this.villager.createChild(this.mate); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityvillager, this.villager, this.mate, null, null, 0).isCancelled()) { ++ return; ++ } + this.mate.setGrowingAge(6000); + this.villager.setGrowingAge(6000); + this.mate.setIsWillingToMate(false); +@@ -124,7 +127,7 @@ + entityvillager = event.getChild(); + entityvillager.setGrowingAge(-24000); + entityvillager.setLocationAndAngles(this.villager.posX, this.villager.posY, this.villager.posZ, 0.0F, 0.0F); +- this.world.spawnEntity(entityvillager); ++ this.world.spawnEntity(entityvillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); + this.world.setEntityState(entityvillager, (byte)12); + } + } diff --git a/patches/net/minecraft/entity/ai/attributes/RangedAttribute.java.patch b/patches/net/minecraft/entity/ai/attributes/RangedAttribute.java.patch new file mode 100644 index 00000000..91243c29 --- /dev/null +++ b/patches/net/minecraft/entity/ai/attributes/RangedAttribute.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/ai/attributes/RangedAttribute.java ++++ ../src-work/minecraft/net/minecraft/entity/ai/attributes/RangedAttribute.java +@@ -6,7 +6,7 @@ + public class RangedAttribute extends BaseAttribute + { + private final double minimumValue; +- private final double maximumValue; ++ public double maximumValue; // Spigot + private String description; + + public RangedAttribute(@Nullable IAttribute parentIn, String unlocalizedNameIn, double defaultValue, double minimumValueIn, double maximumValueIn) +@@ -42,6 +42,8 @@ + + public double clampValue(double value) + { ++ if (value != value) return getDefaultValue(); // CraftBukkit ++ + value = MathHelper.clamp(value, this.minimumValue, this.maximumValue); + return value; + } diff --git a/patches/net/minecraft/entity/boss/EntityDragon.java.patch b/patches/net/minecraft/entity/boss/EntityDragon.java.patch new file mode 100644 index 00000000..97d5b0bb --- /dev/null +++ b/patches/net/minecraft/entity/boss/EntityDragon.java.patch @@ -0,0 +1,176 @@ +--- ../src-base/minecraft/net/minecraft/entity/boss/EntityDragon.java ++++ ../src-work/minecraft/net/minecraft/entity/boss/EntityDragon.java +@@ -19,16 +19,19 @@ + import net.minecraft.entity.item.EntityXPOrb; + import net.minecraft.entity.monster.IMob; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.init.SoundEvents; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.datasync.DataParameter; + import net.minecraft.network.datasync.DataSerializers; + import net.minecraft.network.datasync.EntityDataManager; ++import net.minecraft.network.play.server.SPacketEffect; + import net.minecraft.pathfinding.Path; + import net.minecraft.pathfinding.PathHeap; + import net.minecraft.pathfinding.PathPoint; + import net.minecraft.potion.PotionEffect; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.DamageSource; + import net.minecraft.util.EntityDamageSource; + import net.minecraft.util.EnumParticleTypes; +@@ -40,8 +43,10 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.util.math.Vec3d; ++import net.minecraft.world.Explosion; + import net.minecraft.world.World; + import net.minecraft.world.WorldProviderEnd; ++import net.minecraft.world.WorldServer; + import net.minecraft.world.end.DragonFightManager; + import net.minecraft.world.gen.feature.WorldGenEndPodium; + import net.minecraft.world.storage.loot.LootTableList; +@@ -49,6 +54,8 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.event.entity.EntityExplodeEvent; ++import org.bukkit.event.entity.EntityRegainHealthEvent; + + public class EntityDragon extends EntityLiving implements IEntityMultiPart, IMob + { +@@ -78,6 +85,8 @@ + private final int[] neighbors = new int[24]; + private final PathHeap pathFindQueue = new PathHeap(); + ++ private Explosion explosionSource = new Explosion(null, this, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, true); // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ + public EntityDragon(World worldIn) + { + super(worldIn); +@@ -241,7 +250,7 @@ + + Vec3d vec3d = iphase.getTargetLocation(); + +- if (vec3d != null) ++ if (vec3d != null && iphase.getType() != PhaseList.HOVER) // CraftBukkit - Don't move when hovering + { + double d6 = vec3d.x - this.posX; + double d7 = vec3d.y - this.posY; +@@ -411,7 +420,15 @@ + } + else if (this.ticksExisted % 10 == 0 && this.getHealth() < this.getMaxHealth()) + { +- this.setHealth(this.getHealth() + 1.0F); ++// this.setHealth(this.getHealth() + 1.0F); ++ // CraftBukkit start ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setHealth((float) (this.getHealth() + event.getAmount())); ++ } ++ // CraftBukkit end + } + } + +@@ -489,6 +506,11 @@ + boolean flag = false; + boolean flag1 = false; + ++ // CraftBukkit start - Create a list to hold all the destroyed blocks ++ List destroyedBlocks = new java.util.ArrayList(); ++ org.bukkit.craftbukkit.CraftWorld craftWorld = this.world.getWorld(); ++ // CraftBukkit end ++ + for (int k1 = i; k1 <= l; ++k1) + { + for (int l1 = j; l1 <= i1; ++l1) +@@ -509,7 +531,11 @@ + { + if (block != Blocks.COMMAND_BLOCK && block != Blocks.REPEATING_COMMAND_BLOCK && block != Blocks.CHAIN_COMMAND_BLOCK && block != Blocks.IRON_BARS && block != Blocks.END_GATEWAY) + { +- flag1 = this.world.setBlockToAir(blockpos) || flag1; ++ // CraftBukkit start - Add blocks to list rather than destroying them ++ // flag1 = this.world.setBlockToAir(blockpos) || flag1; ++ flag1 = true; ++ destroyedBlocks.add(craftWorld.getBlockAt(k1, l1, i2)); ++ // CraftBukkit end + } + else + { +@@ -525,6 +551,41 @@ + } + } + ++ // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks ++ org.bukkit.entity.Entity bukkitEntity = this.getBukkitEntity(); ++ EntityExplodeEvent event = new EntityExplodeEvent(bukkitEntity, bukkitEntity.getLocation(), destroyedBlocks, 0F); ++ bukkitEntity.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down. ++ // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled. ++ return flag; ++ } else if (event.getYield() == 0F) { ++ // Yield zero ==> no drops ++ for (org.bukkit.block.Block block : event.blockList()) { ++ this.world.setBlockToAir(new BlockPos(block.getX(), block.getY(), block.getZ())); ++ } ++ } else { ++ for (org.bukkit.block.Block block : event.blockList()) { ++ org.bukkit.Material blockId = block.getType(); ++ if (blockId == org.bukkit.Material.AIR) { ++ continue; ++ } ++ ++ int blockX = block.getX(); ++ int blockY = block.getY(); ++ int blockZ = block.getZ(); ++ ++ Block nmsBlock = org.bukkit.craftbukkit.util.CraftMagicNumbers.getBlock(blockId); ++ if (nmsBlock.canDropFromExplosion(explosionSource)) { ++ nmsBlock.dropBlockAsItemWithChance(this.world, new BlockPos(blockX, blockY, blockZ), nmsBlock.getStateFromMeta(block.getData()), event.getYield(), 0); ++ } ++ nmsBlock.onBlockDestroyedByExplosion(world, new BlockPos(blockX, blockY, blockZ), explosionSource); ++ ++ this.world.setBlockToAir(new BlockPos(blockX, blockY, blockZ)); ++ } ++ } ++ // CraftBukkit end ++ + if (flag1) + { + double d0 = p_70972_1_.minX + (p_70972_1_.maxX - p_70972_1_.minX) * (double)this.rand.nextFloat(); +@@ -638,8 +699,29 @@ + + if (this.deathTicks == 1) + { +- this.world.playBroadcastSound(1028, new BlockPos(this), 0); +- } ++ // CraftBukkit start - Use relative location for far away sounds ++ // Paper start ++ //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; ++ // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (EntityPlayer player : world.playerEntities) { ++ EntityPlayerMP playerMP = (EntityPlayerMP) player; ++ int viewDistance = playerMP.getViewDistance(); ++ // Paper end ++ double deltaX = this.posX - player.posX; ++ double deltaZ = this.posZ - player.posZ; ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ if (world.spigotConfig.dragonDeathSoundRadius > 0 && distanceSquared > world.spigotConfig.dragonDeathSoundRadius * world.spigotConfig.dragonDeathSoundRadius) continue; // Spigot ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.posX + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.posZ + (deltaZ / deltaLength) * viewDistance; ++ playerMP.connection.sendPacket(new SPacketEffect(1028, new BlockPos((int) relativeX, (int) this.posY, (int) relativeZ), 0, true)); ++ } else { ++ playerMP.connection.sendPacket(new SPacketEffect(1028, new BlockPos((int) this.posX, (int) this.posY, (int) this.posZ), 0, true)); ++ } ++ } ++ // CraftBukkit end ++ } + } + + this.move(MoverType.SELF, 0.0D, 0.10000000149011612D, 0.0D); diff --git a/patches/net/minecraft/entity/boss/EntityWither.java.patch b/patches/net/minecraft/entity/boss/EntityWither.java.patch new file mode 100644 index 00000000..26ec27d1 --- /dev/null +++ b/patches/net/minecraft/entity/boss/EntityWither.java.patch @@ -0,0 +1,90 @@ +--- ../src-base/minecraft/net/minecraft/entity/boss/EntityWither.java ++++ ../src-work/minecraft/net/minecraft/entity/boss/EntityWither.java +@@ -34,8 +34,10 @@ + import net.minecraft.network.datasync.DataParameter; + import net.minecraft.network.datasync.DataSerializers; + import net.minecraft.network.datasync.EntityDataManager; ++import net.minecraft.network.play.server.SPacketEffect; + import net.minecraft.pathfinding.PathNavigateGround; + import net.minecraft.potion.PotionEffect; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.DamageSource; + import net.minecraft.util.EntitySelectors; + import net.minecraft.util.EnumParticleTypes; +@@ -47,8 +49,12 @@ + import net.minecraft.world.BossInfoServer; + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.World; ++import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityWither extends EntityMob implements IRangedAttackMob + { +@@ -255,15 +261,41 @@ + + if (j1 <= 0) + { +- this.world.newExplosion(this, this.posX, this.posY + (double)this.getEyeHeight(), this.posZ, 7.0F, false, net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)); +- this.world.playBroadcastSound(1023, new BlockPos(this), 0); ++ // this.world.newExplosion(this, this.posX, this.posY + (double)this.getEyeHeight(), this.posZ, 7.0F, false, net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.world.newExplosion(this, this.posX, this.posY + (double) this.getEyeHeight(), this.posZ, event.getRadius(), event.getFire(), net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)); ++ } ++ // CraftBukkit start - Use relative location for far away sounds ++ // Paper start ++ //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (EntityPlayer player : world.playerEntities) { ++ EntityPlayerMP playerMP = (EntityPlayerMP) player; ++ int viewDistance = playerMP.getViewDistance(); ++ // Paper end ++ double deltaX = this.posX - player.posX; ++ double deltaZ = this.posZ - player.posZ; ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ if (world.spigotConfig.witherSpawnSoundRadius > 0 && distanceSquared > world.spigotConfig.witherSpawnSoundRadius * world.spigotConfig.witherSpawnSoundRadius) continue; // Spigot ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.posX + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.posZ + (deltaZ / deltaLength) * viewDistance; ++ playerMP.connection.sendPacket(new SPacketEffect(1013, new BlockPos((int) relativeX, (int) this.posY, (int) relativeZ), 0, true)); ++ } else { ++ playerMP.connection.sendPacket(new SPacketEffect(1013, new BlockPos((int) this.posX, (int) this.posY, (int) this.posZ), 0, true)); ++ } ++ } ++ // CraftBukkit end + } + + this.setInvulTime(j1); + + if (this.ticksExisted % 10 == 0) + { +- this.heal(10.0F); ++ this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); + } + } + else +@@ -384,6 +416,9 @@ + + if (!block.isAir(iblockstate, this.world, blockpos) && block.canEntityDestroy(iblockstate, world, blockpos, this) && net.minecraftforge.event.ForgeEventFactory.onEntityDestroyBlock(this, blockpos, iblockstate)) + { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, blockpos, Blocks.AIR, 0).isCancelled()) { ++ continue; ++ } + flag = this.world.destroyBlock(blockpos, true) || flag; + } + } +@@ -399,7 +434,7 @@ + + if (this.ticksExisted % 20 == 0) + { +- this.heal(1.0F); ++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); + } + + this.bossInfo.setPercent(this.getHealth() / this.getMaxHealth()); diff --git a/patches/net/minecraft/entity/boss/dragon/phase/PhaseManager.java.patch b/patches/net/minecraft/entity/boss/dragon/phase/PhaseManager.java.patch new file mode 100644 index 00000000..a1b3643e --- /dev/null +++ b/patches/net/minecraft/entity/boss/dragon/phase/PhaseManager.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraft/entity/boss/dragon/phase/PhaseManager.java ++++ ../src-work/minecraft/net/minecraft/entity/boss/dragon/phase/PhaseManager.java +@@ -3,6 +3,8 @@ + import net.minecraft.entity.boss.EntityDragon; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.entity.CraftEnderDragon; ++import org.bukkit.event.entity.EnderDragonChangePhaseEvent; + + public class PhaseManager + { +@@ -26,6 +28,17 @@ + this.phase.removeAreaEffect(); + } + ++ EnderDragonChangePhaseEvent event = new EnderDragonChangePhaseEvent( ++ (CraftEnderDragon) this.dragon.getBukkitEntity(), ++ (this.phase == null) ? null : CraftEnderDragon.getBukkitPhase(this.phase.getType()), ++ CraftEnderDragon.getBukkitPhase(phaseIn) ++ ); ++ this.dragon.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ phaseIn = CraftEnderDragon.getMinecraftPhase(event.getNewPhase()); ++ + this.phase = this.getPhase(phaseIn); + + if (!this.dragon.world.isRemote) diff --git a/patches/net/minecraft/entity/effect/EntityLightningBolt.java.patch b/patches/net/minecraft/entity/effect/EntityLightningBolt.java.patch new file mode 100644 index 00000000..d89f90f9 --- /dev/null +++ b/patches/net/minecraft/entity/effect/EntityLightningBolt.java.patch @@ -0,0 +1,118 @@ +--- ../src-base/minecraft/net/minecraft/entity/effect/EntityLightningBolt.java ++++ ../src-work/minecraft/net/minecraft/entity/effect/EntityLightningBolt.java +@@ -4,14 +4,18 @@ + import net.minecraft.block.material.Material; + import net.minecraft.entity.Entity; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.init.SoundEvents; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.play.server.SPacketSoundEffect; + import net.minecraft.util.SoundCategory; + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.World; ++import net.minecraft.world.WorldServer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityLightningBolt extends EntityWeatherEffect + { +@@ -20,9 +24,13 @@ + private int boltLivingTime; + private final boolean effectOnly; + ++ public boolean isEffect; ++ public boolean isSilent = false; // Spigot ++ + public EntityLightningBolt(World worldIn, double x, double y, double z, boolean effectOnlyIn) + { + super(worldIn); ++ this.isEffect = effectOnlyIn; + this.setLocationAndAngles(x, y, z, 0.0F, 0.0F); + this.lightningState = 2; + this.boltVertex = this.rand.nextLong(); +@@ -34,7 +42,10 @@ + { + if (worldIn.getBlockState(blockpos).getMaterial() == Material.AIR && Blocks.FIRE.canPlaceBlockAt(worldIn, blockpos)) + { +- worldIn.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ // worldIn.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ if (!CraftEventFactory.callBlockIgniteEvent(world, blockpos.getX(), blockpos.getY(), blockpos.getZ(), this).isCancelled()) { ++ world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ } + } + + for (int i = 0; i < 4; ++i) +@@ -43,12 +54,23 @@ + + if (worldIn.getBlockState(blockpos1).getMaterial() == Material.AIR && Blocks.FIRE.canPlaceBlockAt(worldIn, blockpos1)) + { +- worldIn.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ // worldIn.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ if (!CraftEventFactory.callBlockIgniteEvent(world, blockpos1.getX(), blockpos1.getY(), blockpos1.getZ(), this).isCancelled()) { ++ world.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ } + } + } + } + } + ++ // Spigot start ++ public EntityLightningBolt(World world, double d0, double d1, double d2, boolean isEffect, boolean isSilent) ++ { ++ this( world, d0, d1, d2, isEffect ); ++ this.isSilent = isSilent; ++ } ++ // Spigot end ++ + public SoundCategory getSoundCategory() + { + return SoundCategory.WEATHER; +@@ -58,9 +80,25 @@ + { + super.onUpdate(); + +- if (this.lightningState == 2) ++ if (!isSilent && this.lightningState == 2) // Spigot + { +- this.world.playSound((EntityPlayer)null, this.posX, this.posY, this.posZ, SoundEvents.ENTITY_LIGHTNING_THUNDER, SoundCategory.WEATHER, 10000.0F, 0.8F + this.rand.nextFloat() * 0.2F); ++ // CraftBukkit start - Use relative location for far away sounds ++ // this.world.playSound((EntityPlayer)null, this.posX, this.posY, this.posZ, SoundEvents.ENTITY_LIGHTNING_THUNDER, SoundCategory.WEATHER, 10000.0F, 0.8F + this.rand.nextFloat() * 0.2F); ++ float pitch = 0.8F + this.rand.nextFloat() * 0.2F; ++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; ++ for (EntityPlayerMP player : (List) (List) this.world.playerEntities) { ++ double deltaX = this.posX - player.posX; ++ double deltaZ = this.posZ - player.posZ; ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.posX + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.posZ + (deltaZ / deltaLength) * viewDistance; ++ player.connection.sendPacket(new SPacketSoundEffect(SoundEvents.ENTITY_LIGHTNING_THUNDER, SoundCategory.WEATHER, relativeX, this.posY, relativeZ, 10000.0F, pitch)); ++ } else { ++ player.connection.sendPacket(new SPacketSoundEffect(SoundEvents.ENTITY_LIGHTNING_THUNDER, SoundCategory.WEATHER, this.posX, this.posY, this.posZ, 10000.0F, pitch)); ++ } ++ } + this.world.playSound((EntityPlayer)null, this.posX, this.posY, this.posZ, SoundEvents.ENTITY_LIGHTNING_IMPACT, SoundCategory.WEATHER, 2.0F, 0.5F + this.rand.nextFloat() * 0.2F); + } + +@@ -84,13 +122,15 @@ + + if (this.world.getGameRules().getBoolean("doFireTick") && this.world.isAreaLoaded(blockpos, 10) && this.world.getBlockState(blockpos).getMaterial() == Material.AIR && Blocks.FIRE.canPlaceBlockAt(this.world, blockpos)) + { +- this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ if (!isEffect && !CraftEventFactory.callBlockIgniteEvent(world, blockpos.getX(), blockpos.getY(), blockpos.getZ(), this).isCancelled()) { ++ this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ } + } + } + } + } + +- if (this.lightningState >= 0) ++ if (this.lightningState >= 0 && !this.isEffect) // CraftBukkit - add !this.isEffect + { + if (this.world.isRemote) + { diff --git a/patches/net/minecraft/entity/item/EntityArmorStand.java.patch b/patches/net/minecraft/entity/item/EntityArmorStand.java.patch new file mode 100644 index 00000000..cafed871 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityArmorStand.java.patch @@ -0,0 +1,215 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityArmorStand.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityArmorStand.java +@@ -31,13 +31,18 @@ + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.FixTypes; + import net.minecraft.util.datafix.walkers.ItemStackDataLists; +-import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.Rotations; + import net.minecraft.util.math.Vec3d; + import net.minecraft.world.World; + import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.CraftEquipmentSlot; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.ArmorStand; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerArmorStandManipulateEvent; ++import org.bukkit.inventory.EquipmentSlot; + + public class EntityArmorStand extends EntityLivingBase + { +@@ -67,12 +72,12 @@ + public long punchCooldown; + private int disabledSlots; + private boolean wasMarker; +- private Rotations headRotation; +- private Rotations bodyRotation; +- private Rotations leftArmRotation; +- private Rotations rightArmRotation; +- private Rotations leftLegRotation; +- private Rotations rightLegRotation; ++ public Rotations headRotation; ++ public Rotations bodyRotation; ++ public Rotations leftArmRotation; ++ public Rotations rightArmRotation; ++ public Rotations leftLegRotation; ++ public Rotations rightLegRotation; + + public EntityArmorStand(World worldIn) + { +@@ -95,6 +100,13 @@ + this.setPosition(posX, posY, posZ); + } + ++ // CraftBukkit start - SPIGOT-3607, SPIGOT-3637 ++ @Override ++ public float getBukkitYaw() { ++ return this.rotationYaw; ++ } ++ // CraftBukkit end ++ + protected final void setSize(float width, float height) + { + double d0 = this.posX; +@@ -457,6 +469,22 @@ + { + if (!itemstack.isEmpty() || (this.disabledSlots & 1 << p_184795_2_.getSlotIndex() + 16) == 0) + { ++ // CraftBukkit start ++ org.bukkit.inventory.ItemStack armorStandItem = CraftItemStack.asCraftMirror(itemstack); ++ org.bukkit.inventory.ItemStack playerHeldItem = CraftItemStack.asCraftMirror(p_184795_3_); ++ ++ Player bukkitPlayer = (Player) player.getBukkitEntity(); ++ ArmorStand self = (ArmorStand) this.getBukkitEntity(); ++ ++ EquipmentSlot slot = CraftEquipmentSlot.getSlot(p_184795_2_); ++ PlayerArmorStandManipulateEvent armorStandManipulateEvent = new PlayerArmorStandManipulateEvent(bukkitPlayer, self, playerHeldItem, armorStandItem, slot); ++ this.world.getServer().getPluginManager().callEvent(armorStandManipulateEvent); ++ ++ if (armorStandManipulateEvent.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ + if (player.capabilities.isCreativeMode && itemstack.isEmpty() && !p_184795_3_.isEmpty()) + { + ItemStack itemstack2 = p_184795_3_.copy(); +@@ -484,11 +512,16 @@ + + public boolean attackEntityFrom(DamageSource source, float amount) + { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } ++ // CraftBukkit end + if (!this.world.isRemote && !this.isDead) + { + if (DamageSource.OUT_OF_WORLD.equals(source)) + { +- this.setDead(); ++ this.onKillCommand(); // CraftBukkit - this.die() -> this.onKillCommand() + return false; + } + else if (!this.isEntityInvulnerable(source) && !this.canInteract && !this.hasMarker()) +@@ -496,7 +529,7 @@ + if (source.isExplosion()) + { + this.dropContents(); +- this.setDead(); ++ this.onKillCommand(); // CraftBukkit - this.die() -> this.onKillCommand() + return false; + } + else if (DamageSource.IN_FIRE.equals(source)) +@@ -541,7 +574,7 @@ + { + this.playBrokenSound(); + this.playParticles(); +- this.setDead(); ++ this.onKillCommand(); // CraftBukkit - this.die() -> this.onKillCommand() + return false; + } + else +@@ -557,7 +590,7 @@ + { + this.dropBlock(); + this.playParticles(); +- this.setDead(); ++ this.onKillCommand(); // CraftBukkit - this.die() -> this.onKillCommand() + } + + return false; +@@ -623,7 +656,7 @@ + if (f <= 0.5F) + { + this.dropContents(); +- this.setDead(); ++ this.onKillCommand(); // CraftBukkit - this.die() -> this.onKillCommand() + } + else + { +@@ -633,7 +666,8 @@ + + private void dropBlock() + { +- Block.spawnAsEntity(this.world, new BlockPos(this), new ItemStack(Items.ARMOR_STAND)); ++// Block.spawnAsEntity(this.world, new BlockPos(this), new ItemStack(Items.ARMOR_STAND)); ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(new ItemStack(Items.ARMOR_STAND))); // CraftBukkit - add to drops + this.dropContents(); + } + +@@ -647,7 +681,8 @@ + + if (!itemstack.isEmpty()) + { +- Block.spawnAsEntity(this.world, (new BlockPos(this)).up(), itemstack); ++// Block.spawnAsEntity(this.world, (new BlockPos(this)).up(), itemstack); ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -658,7 +693,8 @@ + + if (!itemstack1.isEmpty()) + { +- Block.spawnAsEntity(this.world, (new BlockPos(this)).up(), itemstack1); ++// Block.spawnAsEntity(this.world, (new BlockPos(this)).up(), itemstack1); ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack1)); // CraftBukkit - add to drops + this.armorItems.set(j, ItemStack.EMPTY); + } + } +@@ -791,6 +827,7 @@ + + public void onKillCommand() + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event + this.setDead(); + } + +@@ -804,7 +841,7 @@ + return this.hasMarker() ? EnumPushReaction.IGNORE : super.getPushReaction(); + } + +- private void setSmall(boolean small) ++ public void setSmall(boolean small) + { + this.dataManager.set(STATUS, Byte.valueOf(this.setBit(((Byte)this.dataManager.get(STATUS)).byteValue(), 1, small))); + this.setSize(0.5F, 1.975F); +@@ -815,7 +852,7 @@ + return (((Byte)this.dataManager.get(STATUS)).byteValue() & 1) != 0; + } + +- private void setShowArms(boolean showArms) ++ public void setShowArms(boolean showArms) + { + this.dataManager.set(STATUS, Byte.valueOf(this.setBit(((Byte)this.dataManager.get(STATUS)).byteValue(), 4, showArms))); + } +@@ -825,7 +862,7 @@ + return (((Byte)this.dataManager.get(STATUS)).byteValue() & 4) != 0; + } + +- private void setNoBasePlate(boolean noBasePlate) ++ public void setNoBasePlate(boolean noBasePlate) + { + this.dataManager.set(STATUS, Byte.valueOf(this.setBit(((Byte)this.dataManager.get(STATUS)).byteValue(), 8, noBasePlate))); + } +@@ -835,7 +872,7 @@ + return (((Byte)this.dataManager.get(STATUS)).byteValue() & 8) != 0; + } + +- private void setMarker(boolean marker) ++ public void setMarker(boolean marker) + { + this.dataManager.set(STATUS, Byte.valueOf(this.setBit(((Byte)this.dataManager.get(STATUS)).byteValue(), 16, marker))); + this.setSize(0.5F, 1.975F); +@@ -957,7 +994,7 @@ + return SoundEvents.ENTITY_ARMORSTAND_BREAK; + } + +- public void onStruckByLightning(EntityLightningBolt lightningBolt) ++ public void onStruckByLightning(@Nullable EntityLightningBolt lightningBolt) + { + } + diff --git a/patches/net/minecraft/entity/item/EntityBoat.java.patch b/patches/net/minecraft/entity/item/EntityBoat.java.patch new file mode 100644 index 00000000..cc0d011a --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityBoat.java.patch @@ -0,0 +1,151 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityBoat.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityBoat.java +@@ -36,6 +36,12 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.vehicle.VehicleDamageEvent; ++import org.bukkit.event.vehicle.VehicleDestroyEvent; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++import org.bukkit.event.vehicle.VehicleMoveEvent; + + public class EntityBoat extends Entity + { +@@ -64,6 +70,12 @@ + private EntityBoat.Status previousStatus; + private double lastYd; + ++ // Some of these haven't worked since a few updates, and since 1.9 they are less and less applicable. ++ public double maxSpeed = 0.4D; ++ public double occupiedDeceleration = 0.2D; ++ public double unoccupiedDeceleration = -1; ++ public boolean landBoats = false; ++ + public EntityBoat(World worldIn) + { + super(worldIn); +@@ -138,6 +150,16 @@ + } + else + { ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ org.bukkit.entity.Entity attacker = (source.getTrueSource() == null) ? null : source.getTrueSource().getBukkitEntity(); ++ ++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, (double) amount); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return true; ++ } ++ // amount = event.getDamage(); // TODO Why don't we do this? + this.setForwardDirection(-this.getForwardDirection()); + this.setTimeSinceHit(10); + this.setDamageTaken(this.getDamageTaken() + amount * 10.0F); +@@ -146,6 +168,13 @@ + + if (flag || this.getDamageTaken() > 40.0F) + { ++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker); ++ this.world.getServer().getPluginManager().callEvent(destroyEvent); ++ ++ if (destroyEvent.isCancelled()) { ++ this.setDamageTaken(40F); // Maximize damage so this doesn't get triggered again right away ++ return true; ++ } + if (!flag && this.world.getGameRules().getBoolean("doEntityDrops")) + { + this.dropItemWithOffset(this.getItemBoat(), 1, 0.0F); +@@ -169,11 +198,23 @@ + { + if (entityIn.getEntityBoundingBox().minY < this.getEntityBoundingBox().maxY) + { ++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entityIn.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } + super.applyEntityCollision(entityIn); + } + } + else if (entityIn.getEntityBoundingBox().minY <= this.getEntityBoundingBox().minY) + { ++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entityIn.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } + super.applyEntityCollision(entityIn); + } + } +@@ -227,12 +268,13 @@ + return this.getHorizontalFacing().rotateY(); + } + ++ private Location lastLocation; + public void onUpdate() + { + this.previousStatus = this.status; + this.status = this.getBoatStatus(); + +- if (this.status != EntityBoat.Status.UNDER_WATER && this.status != EntityBoat.Status.UNDER_FLOWING_WATER) ++ if (this.status != Status.UNDER_WATER && this.status != Status.UNDER_FLOWING_WATER) + { + this.outOfControlTicks = 0.0F; + } +@@ -286,6 +328,20 @@ + this.motionZ = 0.0D; + } + ++ org.bukkit.Server server = this.world.getServer(); ++ org.bukkit.World bworld = this.world.getWorld(); ++ ++ Location to = new Location(bworld, this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch); ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ ++ server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle)); ++ ++ if (lastLocation != null && !lastLocation.equals(to)) { ++ VehicleMoveEvent event = new VehicleMoveEvent(vehicle, lastLocation, to); ++ server.getPluginManager().callEvent(event); ++ } ++ lastLocation = vehicle.getLocation(); ++ + for (int i = 0; i <= 1; ++i) + { + if (this.getPaddleState(i)) +@@ -850,19 +906,20 @@ + + if (!this.world.isRemote && !this.isDead) + { +- this.setDead(); ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, null); ++ this.world.getServer().getPluginManager().callEvent(destroyEvent); ++ if (!destroyEvent.isCancelled()) { ++ this.setDead(); ++ if (this.world.getGameRules().getBoolean("doEntityDrops")) { ++ for (int i = 0; i < 3; ++i) { ++ this.entityDropItem(new ItemStack(Item.getItemFromBlock(Blocks.PLANKS), 1, this.getBoatType().getMetadata()), 0.0F); ++ } + +- if (this.world.getGameRules().getBoolean("doEntityDrops")) +- { +- for (int i = 0; i < 3; ++i) +- { +- this.entityDropItem(new ItemStack(Item.getItemFromBlock(Blocks.PLANKS), 1, this.getBoatType().getMetadata()), 0.0F); ++ for (int j = 0; j < 2; ++j) { ++ this.dropItemWithOffset(Items.STICK, 1, 0.0F); ++ } + } +- +- for (int j = 0; j < 2; ++j) +- { +- this.dropItemWithOffset(Items.STICK, 1, 0.0F); +- } + } + } + } diff --git a/patches/net/minecraft/entity/item/EntityEnderCrystal.java.patch b/patches/net/minecraft/entity/item/EntityEnderCrystal.java.patch new file mode 100644 index 00000000..d31c98a2 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityEnderCrystal.java.patch @@ -0,0 +1,48 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityEnderCrystal.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityEnderCrystal.java +@@ -17,6 +17,8 @@ + import net.minecraft.world.end.DragonFightManager; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityEnderCrystal extends Entity + { +@@ -62,7 +64,10 @@ + + if (this.world.provider instanceof WorldProviderEnd && this.world.getBlockState(blockpos).getBlock() != Blocks.FIRE) + { +- this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++// this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ if (!CraftEventFactory.callBlockIgniteEvent(this.world, blockpos.getX(), blockpos.getY(), blockpos.getZ(), this).isCancelled()) { ++ this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ } + } + } + } +@@ -109,13 +114,23 @@ + { + if (!this.isDead && !this.world.isRemote) + { ++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } + this.setDead(); + + if (!this.world.isRemote) + { + if (!source.isExplosion()) + { +- this.world.createExplosion((Entity)null, this.posX, this.posY, this.posZ, 6.0F, true); ++// this.world.createExplosion((Entity)null, this.posX, this.posY, this.posZ, 6.0F, true); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 6.0F, true); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.isDead = false; ++ return false; ++ } ++ this.world.createExplosion(this, this.posX, this.posY, this.posZ, event.getRadius(), event.getFire()); + } + + this.onCrystalDestroyed(source); diff --git a/patches/net/minecraft/entity/item/EntityEnderEye.java.patch b/patches/net/minecraft/entity/item/EntityEnderEye.java.patch new file mode 100644 index 00000000..550dd063 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityEnderEye.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityEnderEye.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityEnderEye.java +@@ -14,11 +14,11 @@ + + public class EntityEnderEye extends Entity + { +- private double targetX; +- private double targetY; +- private double targetZ; +- private int despawnTimer; +- private boolean shatterOrDrop; ++ public double targetX; ++ public double targetY; ++ public double targetZ; ++ public int despawnTimer; ++ public boolean shatterOrDrop; + + public EntityEnderEye(World worldIn) + { diff --git a/patches/net/minecraft/entity/item/EntityEnderPearl.java.patch b/patches/net/minecraft/entity/item/EntityEnderPearl.java.patch new file mode 100644 index 00000000..74cd205d --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityEnderPearl.java.patch @@ -0,0 +1,70 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityEnderPearl.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityEnderPearl.java +@@ -18,6 +18,11 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; + + public class EntityEnderPearl extends EntityThrowable + { +@@ -96,27 +101,37 @@ + { + EntityPlayerMP entityplayermp = (EntityPlayerMP)entitylivingbase; + +- if (entityplayermp.connection.getNetworkManager().isChannelOpen() && entityplayermp.world == this.world && !entityplayermp.isPlayerSleeping()) +- { ++ if (entityplayermp.connection.getNetworkManager().isChannelOpen() && entityplayermp.world == this.world && !entityplayermp.isPlayerSleeping()) { + net.minecraftforge.event.entity.living.EnderTeleportEvent event = new net.minecraftforge.event.entity.living.EnderTeleportEvent(entityplayermp, this.posX, this.posY, this.posZ, 5.0F); +- if (!net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) +- { // Don't indent to lower patch size +- if (this.rand.nextFloat() < 0.05F && this.world.getGameRules().getBoolean("doMobSpawning")) +- { +- EntityEndermite entityendermite = new EntityEndermite(this.world); +- entityendermite.setSpawnedByPlayer(true); +- entityendermite.setLocationAndAngles(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, entitylivingbase.rotationYaw, entitylivingbase.rotationPitch); +- this.world.spawnEntity(entityendermite); +- } ++ if (!net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) { // Don't indent to lower patch size ++ // CraftBukkit start - Fire PlayerTeleportEvent ++ org.bukkit.craftbukkit.entity.CraftPlayer player = (CraftPlayer) entityplayermp.getBukkitEntity(); ++ org.bukkit.Location location = getBukkitEntity().getLocation(); ++ location.setPitch(player.getLocation().getPitch()); ++ location.setYaw(player.getLocation().getYaw()); + +- if (entitylivingbase.isRiding()) +- { +- entitylivingbase.dismountRidingEntity(); +- } ++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.ENDER_PEARL); ++ Bukkit.getPluginManager().callEvent(teleEvent); + +- entitylivingbase.setPositionAndUpdate(event.getTargetX(), event.getTargetY(), event.getTargetZ()); +- entitylivingbase.fallDistance = 0.0F; +- entitylivingbase.attackEntityFrom(DamageSource.FALL, event.getAttackDamage()); ++ if (!teleEvent.isCancelled() && !entityplayermp.connection.isDisconnected()) { ++ if (this.rand.nextFloat() < 0.05F && this.world.getGameRules().getBoolean("doMobSpawning")) { ++ EntityEndermite entityendermite = new EntityEndermite(this.world); ++ entityendermite.setSpawnedByPlayer(true); ++ entityendermite.setLocationAndAngles(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, entitylivingbase.rotationYaw, entitylivingbase.rotationPitch); ++ this.world.spawnEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); ++ } ++ ++ if (entitylivingbase.isRiding()) { ++ entitylivingbase.dismountRidingEntity(); ++ } ++ ++ entityplayermp.connection.teleport(teleEvent.getTo()); ++ entitylivingbase.fallDistance = 0.0F; ++ CraftEventFactory.entityDamage = this; ++ entitylivingbase.attackEntityFrom(DamageSource.FALL, 5.0F); ++ CraftEventFactory.entityDamage = null; ++ } ++ // CraftBukkit end + } + } + } diff --git a/patches/net/minecraft/entity/item/EntityExpBottle.java.patch b/patches/net/minecraft/entity/item/EntityExpBottle.java.patch new file mode 100644 index 00000000..251b1ba1 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityExpBottle.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityExpBottle.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityExpBottle.java +@@ -40,9 +40,16 @@ + { + if (!this.world.isRemote) + { +- this.world.playEvent(2002, new BlockPos(this), PotionUtils.getPotionColor(PotionTypes.WATER)); ++ // CraftBukkit - moved to after event ++// this.world.playEvent(2002, new BlockPos(this), PotionUtils.getPotionColor(PotionTypes.WATER)); + int i = 3 + this.world.rand.nextInt(5) + this.world.rand.nextInt(5); + ++ org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, i); ++ i = event.getExperience(); ++ if (event.getShowEffect()) { ++ this.world.playEvent(2002, new BlockPos(this), PotionUtils.getPotionColor(PotionTypes.WATER)); ++ } ++ + while (i > 0) + { + int j = EntityXPOrb.getXPSplit(i); diff --git a/patches/net/minecraft/entity/item/EntityFallingBlock.java.patch b/patches/net/minecraft/entity/item/EntityFallingBlock.java.patch new file mode 100644 index 00000000..4601450e --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityFallingBlock.java.patch @@ -0,0 +1,68 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityFallingBlock.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityFallingBlock.java +@@ -31,6 +31,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityFallingBlock extends Entity + { +@@ -38,7 +39,7 @@ + public int fallTime; + public boolean shouldDropItem = true; + private boolean dontSetBlock; +- private boolean hurtEntities; ++ public boolean hurtEntities; + private int fallHurtMax = 40; + private float fallHurtAmount = 2.0F; + public NBTTagCompound tileEntityData; +@@ -114,7 +115,7 @@ + { + BlockPos blockpos = new BlockPos(this); + +- if (this.world.getBlockState(blockpos).getBlock() == block) ++ if (this.world.getBlockState(blockpos).getBlock() == block && !CraftEventFactory.callEntityChangeBlockEvent(this, blockpos, Blocks.AIR, 0).isCancelled()) + { + this.world.setBlockToAir(blockpos); + } +@@ -170,7 +171,7 @@ + if (!flag1 && BlockFalling.canFallThrough(this.world.getBlockState(new BlockPos(this.posX, this.posY - 0.009999999776482582D, this.posZ)))) + { + this.onGround = false; +- return; ++// return; // CraftBukkit + } + + this.motionX *= 0.699999988079071D; +@@ -185,10 +186,20 @@ + { + if (this.world.mayPlace(block, blockpos1, true, EnumFacing.UP, this) && (flag1 || !BlockFalling.canFallThrough(this.world.getBlockState(blockpos1.down()))) && this.world.setBlockState(blockpos1, this.fallTile, 3)) + { ++ // CraftBukkit start ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, blockpos1, this.fallTile.getBlock(), this.fallTile.getBlock().getMetaFromState(this.fallTile)).isCancelled()) { ++ return; ++ } ++ this.world.setBlockState(blockpos1, this.fallTile, 3); + if (block instanceof BlockFalling) + { + ((BlockFalling)block).onEndFalling(this.world, blockpos1, this.fallTile, iblockstate); + } ++ // CraftBukkit end ++ if (block instanceof BlockFalling) ++ { ++ ((BlockFalling)block).onEndFalling(this.world, blockpos1, this.fallTile, iblockstate); ++ } + + if (this.tileEntityData != null && block.hasTileEntity(this.fallTile)) + { +@@ -248,7 +259,9 @@ + + for (Entity entity : list) + { ++ CraftEventFactory.entityDamage = this; // CraftBukkit + entity.attackEntityFrom(damagesource, (float)Math.min(MathHelper.floor((float)i * this.fallHurtAmount), this.fallHurtMax)); ++ CraftEventFactory.entityDamage = null; // CraftBukkit + } + + if (flag && (double)this.rand.nextFloat() < 0.05000000074505806D + (double)i * 0.05D) diff --git a/patches/net/minecraft/entity/item/EntityFireworkRocket.java.patch b/patches/net/minecraft/entity/item/EntityFireworkRocket.java.patch new file mode 100644 index 00000000..7a750bef --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityFireworkRocket.java.patch @@ -0,0 +1,67 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityFireworkRocket.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityFireworkRocket.java +@@ -23,13 +23,14 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityFireworkRocket extends Entity + { +- private static final DataParameter FIREWORK_ITEM = EntityDataManager.createKey(EntityFireworkRocket.class, DataSerializers.ITEM_STACK); ++ public static final DataParameter FIREWORK_ITEM = EntityDataManager.createKey(EntityFireworkRocket.class, DataSerializers.ITEM_STACK); + private static final DataParameter BOOSTED_ENTITY_ID = EntityDataManager.createKey(EntityFireworkRocket.class, DataSerializers.VARINT); + private int fireworkAge; +- private int lifetime; ++ public int lifetime; + private EntityLivingBase boostedEntity; + + public EntityFireworkRocket(World worldIn) +@@ -38,6 +39,14 @@ + this.setSize(0.25F, 0.25F); + } + ++ // Spigot Start ++ @Override ++ public void inactiveTick() { ++ this.fireworkAge += 1; ++ super.inactiveTick(); ++ } ++ // Spigot End ++ + protected void entityInit() + { + this.dataManager.register(FIREWORK_ITEM, ItemStack.EMPTY); +@@ -187,8 +196,10 @@ + + if (!this.world.isRemote && this.fireworkAge > this.lifetime) + { +- this.world.setEntityState(this, (byte)17); +- this.dealExplosionDamage(); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { ++ this.world.setEntityState(this, (byte) 17); ++ this.dealExplosionDamage(); ++ } + this.setDead(); + } + } +@@ -209,7 +220,9 @@ + { + if (this.boostedEntity != null) + { ++ CraftEventFactory.entityDamage = this; + this.boostedEntity.attackEntityFrom(DamageSource.FIREWORKS, (float)(5 + nbttaglist.tagCount() * 2)); ++ CraftEventFactory.entityDamage = null; + } + + double d0 = 5.0D; +@@ -235,7 +248,9 @@ + if (flag) + { + float f1 = f * (float)Math.sqrt((5.0D - (double)this.getDistance(entitylivingbase)) / 5.0D); ++ CraftEventFactory.entityDamage = this; + entitylivingbase.attackEntityFrom(DamageSource.FIREWORKS, f1); ++ CraftEventFactory.entityDamage = null; + } + } + } diff --git a/patches/net/minecraft/entity/item/EntityItem.java.patch b/patches/net/minecraft/entity/item/EntityItem.java.patch new file mode 100644 index 00000000..7e26b2ee --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityItem.java.patch @@ -0,0 +1,192 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityItem.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityItem.java +@@ -13,6 +13,7 @@ + import net.minecraft.network.datasync.DataParameter; + import net.minecraft.network.datasync.DataSerializers; + import net.minecraft.network.datasync.EntityDataManager; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.stats.StatList; + import net.minecraft.util.DamageSource; + import net.minecraft.util.datafix.DataFixer; +@@ -26,13 +27,15 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.event.entity.EntityPickupItemEvent; ++import org.bukkit.event.player.PlayerPickupItemEvent; + + public class EntityItem extends Entity + { + private static final Logger LOGGER = LogManager.getLogger(); + private static final DataParameter ITEM = EntityDataManager.createKey(EntityItem.class, DataSerializers.ITEM_STACK); + private int age; +- private int pickupDelay; ++ public int pickupDelay; + private int health; + private String thrower; + private String owner; +@@ -43,6 +46,8 @@ + */ + public int lifespan = 6000; + ++ private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit ++ + public EntityItem(World worldIn, double x, double y, double z) + { + super(worldIn); +@@ -60,7 +65,7 @@ + { + this(worldIn, x, y, z); + this.setItem(stack); +- this.lifespan = (stack.getItem() == null ? 6000 : stack.getItem().getEntityLifespan(stack, worldIn)); ++ this.lifespan = (stack.getItem() == null ? world.spigotConfig.itemDespawnRate : stack.getItem().getEntityLifespan(stack, worldIn)); // Spigot + } + + protected boolean canTriggerWalking() +@@ -93,10 +98,12 @@ + { + super.onUpdate(); + +- if (this.pickupDelay > 0 && this.pickupDelay != 32767) +- { +- --this.pickupDelay; +- } ++ // CraftBukkit start - Use wall time for pickup and despawn timers ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ if (this.age != -32768) this.age += elapsedTicks; ++ this.lastTick = MinecraftServer.currentTick; ++ // CraftBukkit end + + this.prevPosX = this.posX; + this.prevPosY = this.posY; +@@ -156,10 +163,12 @@ + this.motionY *= -0.5D; + } + ++ /* Craftbukkit start - moved up + if (this.age != -32768) + { + ++this.age; + } ++ // Craftbukkit end */ + + this.handleWaterMovement(); + +@@ -180,6 +189,12 @@ + + if (!this.world.isRemote && this.age >= lifespan) + { ++ // CraftBukkit start - fire ItemDespawnEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { ++ this.age = 0; ++ return; ++ } ++ // CraftBukkit end + int hook = net.minecraftforge.event.ForgeEventFactory.onItemExpire(this, item); + if (hook < 0) this.setDead(); + else this.lifespan += hook; +@@ -191,9 +206,34 @@ + } + } + ++ // Spigot start - copied from above ++ @Override ++ public void inactiveTick() { ++ // CraftBukkit start - Use wall time for pickup and despawn timers ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ if (this.age != -32768) this.age += elapsedTicks; ++ this.lastTick = MinecraftServer.currentTick; ++ // CraftBukkit end ++ ++ if (!this.world.isRemote && this.age >= world.spigotConfig.itemDespawnRate) { // Spigot ++ // CraftBukkit start - fire ItemDespawnEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { ++ this.age = 0; ++ return; ++ } ++ // CraftBukkit end ++ this.setDead(); ++ } ++ } ++ // Spigot end ++ + private void searchForOtherItemsNearby() + { +- for (EntityItem entityitem : this.world.getEntitiesWithinAABB(EntityItem.class, this.getEntityBoundingBox().grow(0.5D, 0.0D, 0.5D))) ++ // Spigot start ++ double radius = world.spigotConfig.itemMerge; ++ for (EntityItem entityitem : this.world.getEntitiesWithinAABB(EntityItem.class, this.getEntityBoundingBox().grow(radius, radius, radius))) ++ // Spigot end + { + this.combineItems(entityitem); + } +@@ -248,11 +288,14 @@ + } + else + { +- itemstack1.grow(itemstack.getCount()); +- other.pickupDelay = Math.max(other.pickupDelay, this.pickupDelay); +- other.age = Math.min(other.age, this.age); +- other.setItem(itemstack1); +- this.setDead(); ++ // Spigot start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemMergeEvent(other, this).isCancelled()) return false; ++ itemstack.grow(itemstack1.getCount()); ++ this.pickupDelay = Math.max(other.pickupDelay, this.pickupDelay); ++ this.age = Math.min(other.age, this.age); ++ this.setItem(itemstack); ++ other.setDead(); ++ // Spigot end + return true; + } + } +@@ -314,6 +357,11 @@ + } + else + { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } ++ // CraftBukkit end + this.markVelocityChanged(); + this.health = (int)((float)this.health - amount); + +@@ -393,6 +441,35 @@ + Item item = itemstack.getItem(); + int i = itemstack.getCount(); + ++ // CraftBukkit start - fire PlayerPickupItemEvent ++ int canHold = entityIn.inventory.canHold(itemstack); ++ int remaining = i - canHold; ++ ++ if (this.pickupDelay <= 0 && canHold > 0) { ++ itemstack.setCount(canHold); ++ // Call legacy event ++ PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((org.bukkit.entity.Player) entityIn.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ playerEvent.setCancelled(!entityIn.thisisatest); ++ this.world.getServer().getPluginManager().callEvent(playerEvent); ++ if (playerEvent.isCancelled()) { ++ return; ++ } ++ ++ // Call newer event afterwards ++ EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((org.bukkit.entity.Player) entityIn.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ entityEvent.setCancelled(!entityIn.thisisatest); ++ this.world.getServer().getPluginManager().callEvent(entityEvent); ++ if (entityEvent.isCancelled()) { ++ return; ++ } ++ ++ itemstack.setCount(canHold + remaining); ++ ++ // Possibly < 0; fix here so we do not have to modify code below ++ this.pickupDelay = 0; ++ } ++ // CraftBukkit end ++ + int hook = net.minecraftforge.event.ForgeEventFactory.onItemPickup(this, entityIn); + if (hook < 0) return; + ItemStack clone = itemstack.copy(); diff --git a/patches/net/minecraft/entity/item/EntityItemFrame.java.patch b/patches/net/minecraft/entity/item/EntityItemFrame.java.patch new file mode 100644 index 00000000..1146b0a0 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityItemFrame.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityItemFrame.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityItemFrame.java +@@ -63,6 +63,9 @@ + { + if (!this.world.isRemote) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false) || this.isDead) { ++ return true; ++ } + this.dropItemOrSelf(source.getTrueSource(), false); + this.playSound(SoundEvents.ENTITY_ITEMFRAME_REMOVE_ITEM, 1.0F, 1.0F); + this.setDisplayedItem(ItemStack.EMPTY); diff --git a/patches/net/minecraft/entity/item/EntityMinecart.java.patch b/patches/net/minecraft/entity/item/EntityMinecart.java.patch new file mode 100644 index 00000000..9503e4cd --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityMinecart.java.patch @@ -0,0 +1,268 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityMinecart.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityMinecart.java +@@ -35,6 +35,12 @@ + import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.vehicle.VehicleDamageEvent; ++import org.bukkit.event.vehicle.VehicleDestroyEvent; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++import org.bukkit.util.Vector; + + public abstract class EntityMinecart extends Entity implements IWorldNameable + { +@@ -73,6 +79,17 @@ + protected float maxSpeedAirVertical = defaultMaxSpeedAirVertical; + protected double dragAir = defaultDragAir; + ++ // CraftBukkit start - Use Forge vars from above ++ public boolean slowWhenEmpty = true; ++ private double derailedX = 0.5; ++ private double derailedY = 0.5; ++ private double derailedZ = 0.5; ++ private double flyingX = dragAir; ++ private double flyingY = dragAir; ++ private double flyingZ = dragAir; ++ public double maxSpeed = 0.4D; ++ // CraftBukkit end ++ + public EntityMinecart(World worldIn) + { + super(worldIn); +@@ -162,6 +179,20 @@ + } + else + { ++ // CraftBukkit start - fire VehicleDamageEvent ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ org.bukkit.entity.Entity passenger = (source.getTrueSource() == null) ? null : source.getTrueSource().getBukkitEntity(); ++ ++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, passenger, amount); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return true; ++ } ++ ++ amount = (float) event.getDamage(); ++ // CraftBukkit end ++ + this.setRollingDirection(-this.getRollingDirection()); + this.setRollingAmplitude(10); + this.markVelocityChanged(); +@@ -170,6 +201,16 @@ + + if (flag || this.getDamage() > 40.0F) + { ++ // CraftBukkit start ++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, passenger); ++ this.world.getServer().getPluginManager().callEvent(destroyEvent); ++ ++ if (destroyEvent.isCancelled()) { ++ this.setDamage(40); // Maximize damage so this doesn't get triggered again right away ++ return true; ++ } ++ // CraftBukkit end ++ + this.removePassengers(); + + if (flag && !this.hasCustomName()) +@@ -228,6 +269,14 @@ + + public void onUpdate() + { ++ // CraftBukkit start ++ double prevX = this.posX; ++ double prevY = this.posY; ++ double prevZ = this.posZ; ++ float prevYaw = this.rotationYaw; ++ float prevPitch = this.rotationPitch; ++ // CraftBukkit end ++ + if (this.getRollingAmplitude() > 0) + { + this.setRollingAmplitude(this.getRollingAmplitude() - 1); +@@ -243,6 +292,8 @@ + this.outOfWorld(); + } + ++ // CraftBukkit - handled in postTick ++ /* + if (!this.world.isRemote && this.world instanceof WorldServer) + { + this.world.profiler.startSection("portal"); +@@ -294,6 +345,7 @@ + + this.world.profiler.endSection(); + } ++ */ + + if (this.world.isRemote) + { +@@ -381,6 +433,19 @@ + if (getCollisionHandler() != null) box = getCollisionHandler().getMinecartCollisionBox(this); + else box = this.getEntityBoundingBox().grow(0.20000000298023224D, 0.0D, 0.20000000298023224D); + ++ // CraftBukkit start ++ org.bukkit.World bworld = this.world.getWorld(); ++ Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch); ++ Location to = new Location(bworld, this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch); ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ ++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle)); ++ ++ if (!from.equals(to)) { ++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to)); ++ } ++ // CraftBukkit end ++ + if (canBeRidden() && this.motionX * this.motionX + this.motionZ * this.motionZ > 0.01D) + { + List list = this.world.getEntitiesInAABBexcluding(this, box, EntitySelectors.getTeamCollisionPredicate(this)); +@@ -393,10 +458,26 @@ + + if (!(entity1 instanceof EntityPlayer) && !(entity1 instanceof EntityIronGolem) && !(entity1 instanceof EntityMinecart) && !this.isBeingRidden() && !entity1.isRiding()) + { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity1.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + entity1.startRiding(this); + } + else + { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity1.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + entity1.applyEntityCollision(this); + } + } +@@ -408,6 +489,12 @@ + { + if (!this.isPassenger(entity) && entity.canBePushed() && entity instanceof EntityMinecart) + { ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } + entity.applyEntityCollision(this); + } + } +@@ -420,7 +507,7 @@ + + protected double getMaximumSpeed() + { +- return 0.4D; ++ return this.maxSpeed; // CraftBukkit + } + + public void onActivatorRailPass(int x, int y, int z, boolean receivingPower) +@@ -446,18 +533,28 @@ + + if (this.onGround) + { +- this.motionX *= 0.5D; +- this.motionY *= 0.5D; +- this.motionZ *= 0.5D; ++ // CraftBukkit start - replace magic numbers with our variables ++ // this.motionX *= 0.5D; ++ // this.motionY *= 0.5D; ++ // this.motionZ *= 0.5D; ++ this.motionX *= this.derailedX; ++ this.motionY *= this.derailedY; ++ this.motionZ *= this.derailedZ; ++ // CraftBukkit end + } + + this.move(MoverType.SELF, this.motionX, moveY, this.motionZ); + + if (!this.onGround) + { +- this.motionX *= getDragAir(); +- this.motionY *= getDragAir(); +- this.motionZ *= getDragAir(); ++ // CraftBukkit start - replace magic numbers with our variables ++ // this.motionX *= getDragAir(); ++ // this.motionY *= getDragAir(); ++ // this.motionZ *= getDragAir(); ++ this.motionX *= this.flyingX; ++ this.motionY *= this.flyingY; ++ this.motionZ *= this.flyingZ; ++ // CraftBukkit end + } + } + +@@ -668,7 +765,7 @@ + + protected void applyDrag() + { +- if (this.isBeingRidden()) ++ if (this.isBeingRidden() || !this.slowWhenEmpty) // CraftBukkit - add !this.slowWhenEmpty + { + this.motionX *= 0.996999979019165D; + this.motionY *= 0.0D; +@@ -868,6 +965,14 @@ + { + if (!this.isPassenger(entityIn)) + { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entityIn.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + double d0 = entityIn.posX - this.posX; + double d1 = entityIn.posZ - this.posZ; + double d2 = d0 * d0 + d1 * d1; +@@ -1260,6 +1365,9 @@ + public void setDragAir(double value) + { + dragAir = value; ++ flyingX = dragAir; ++ flyingY = dragAir; ++ flyingZ = dragAir; + } + + public double getSlopeAdjustment() +@@ -1322,4 +1430,26 @@ + } + } + } ++ ++ // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers ++ public Vector getFlyingVelocityMod() { ++ return new Vector(flyingX, flyingY, flyingZ); ++ } ++ ++ public void setFlyingVelocityMod(Vector flying) { ++ flyingX = flying.getX(); ++ flyingY = flying.getY(); ++ flyingZ = flying.getZ(); ++ } ++ ++ public Vector getDerailedVelocityMod() { ++ return new Vector(derailedX, derailedY, derailedZ); ++ } ++ ++ public void setDerailedVelocityMod(Vector derailed) { ++ derailedX = derailed.getX(); ++ derailedY = derailed.getY(); ++ derailedZ = derailed.getZ(); ++ } ++ // CraftBukkit end + } diff --git a/patches/net/minecraft/entity/item/EntityMinecartCommandBlock.java.patch b/patches/net/minecraft/entity/item/EntityMinecartCommandBlock.java.patch new file mode 100644 index 00000000..607fe58e --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityMinecartCommandBlock.java.patch @@ -0,0 +1,17 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityMinecartCommandBlock.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityMinecartCommandBlock.java +@@ -29,10 +29,13 @@ + + public class EntityMinecartCommandBlock extends EntityMinecart + { +- private static final DataParameter COMMAND = EntityDataManager.createKey(EntityMinecartCommandBlock.class, DataSerializers.STRING); ++ public static final DataParameter COMMAND = EntityDataManager.createKey(EntityMinecartCommandBlock.class, DataSerializers.STRING); + private static final DataParameter LAST_OUTPUT = EntityDataManager.createKey(EntityMinecartCommandBlock.class, DataSerializers.TEXT_COMPONENT); + private final CommandBlockBaseLogic commandBlockLogic = new CommandBlockBaseLogic() + { ++ { ++ this.sender = EntityMinecartCommandBlock.this.getBukkitEntity(); // CraftBukkit - Set the sender ++ } + public void updateCommand() + { + EntityMinecartCommandBlock.this.getDataManager().set(EntityMinecartCommandBlock.COMMAND, this.getCommand()); diff --git a/patches/net/minecraft/entity/item/EntityMinecartContainer.java.patch b/patches/net/minecraft/entity/item/EntityMinecartContainer.java.patch new file mode 100644 index 00000000..4bcfce23 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityMinecartContainer.java.patch @@ -0,0 +1,81 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityMinecartContainer.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityMinecartContainer.java +@@ -1,5 +1,6 @@ + package net.minecraft.entity.item; + ++import java.util.List; + import java.util.Random; + import javax.annotation.Nullable; + import net.minecraft.entity.Entity; +@@ -23,14 +24,33 @@ + import net.minecraft.world.storage.loot.ILootContainer; + import net.minecraft.world.storage.loot.LootContext; + import net.minecraft.world.storage.loot.LootTable; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.inventory.InventoryHolder; + + public abstract class EntityMinecartContainer extends EntityMinecart implements ILockableContainer, ILootContainer + { +- private NonNullList minecartContainerItems = NonNullList.withSize(36, ItemStack.EMPTY); ++ private NonNullList minecartContainerItems = NonNullList.withSize(this.getSizeInventory(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 + public boolean dropContentsWhenDead = true; + private ResourceLocation lootTable; + private long lootTableSeed; ++ private int maxStack = MAX_STACK; // CraftBukkit + ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ + public EntityMinecartContainer(World worldIn) + { + super(worldIn); +@@ -41,6 +61,27 @@ + super(worldIn, x, y, z); + } + ++ // CraftBukkit start ++ public List getContents() { ++ return this.minecartContainerItems; ++ } ++ ++ public InventoryHolder getOwner() { ++ org.bukkit.entity.Entity cart = getBukkitEntity(); ++ if(cart instanceof InventoryHolder) return (InventoryHolder) cart; ++ return null; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return getBukkitEntity().getLocation(); ++ } ++ // CraftBukkit end ++ + public void killMinecart(DamageSource source) + { + super.killMinecart(source); +@@ -134,7 +175,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + @Nullable diff --git a/patches/net/minecraft/entity/item/EntityPainting.java.patch b/patches/net/minecraft/entity/item/EntityPainting.java.patch new file mode 100644 index 00000000..f273b017 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityPainting.java.patch @@ -0,0 +1,10 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityPainting.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityPainting.java +@@ -24,6 +24,7 @@ + public EntityPainting(World worldIn) + { + super(worldIn); ++ this.art = EnumArt.values()[this.rand.nextInt(EnumArt.values().length)]; // CraftBukkit - generate a non-null painting + } + + public EntityPainting(World worldIn, BlockPos pos, EnumFacing facing) diff --git a/patches/net/minecraft/entity/item/EntityTNTPrimed.java.patch b/patches/net/minecraft/entity/item/EntityTNTPrimed.java.patch new file mode 100644 index 00000000..612c287f --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityTNTPrimed.java.patch @@ -0,0 +1,62 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityTNTPrimed.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityTNTPrimed.java +@@ -10,6 +10,7 @@ + import net.minecraft.network.datasync.EntityDataManager; + import net.minecraft.util.EnumParticleTypes; + import net.minecraft.world.World; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityTNTPrimed extends Entity + { +@@ -18,6 +19,9 @@ + private EntityLivingBase tntPlacedBy; + private int fuse; + ++ public float yield = 4; // CraftBukkit - add field ++ public boolean isIncendiary = false; // CraftBukkit - add field ++ + public EntityTNTPrimed(World worldIn) + { + super(worldIn); +@@ -59,6 +63,7 @@ + + public void onUpdate() + { ++ if (world.spigotConfig.currentPrimedTnt++ > world.spigotConfig.maxTntTicksPerTick) { return; } // Spigot + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; +@@ -84,12 +89,14 @@ + + if (this.fuse <= 0) + { +- this.setDead(); ++ // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event ++ // this.setDead(); + + if (!this.world.isRemote) + { + this.explode(); + } ++ this.setDead(); + } + else + { +@@ -100,8 +107,15 @@ + + private void explode() + { +- float f = 4.0F; +- this.world.createExplosion(this, this.posX, this.posY + (double)(this.height / 16.0F), this.posZ, 4.0F, true); ++ // CraftBukkit start ++ org.bukkit.craftbukkit.CraftServer server = this.world.getServer(); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) org.bukkit.craftbukkit.entity.CraftEntity.getEntity(server, this)); ++ server.getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.world.newExplosion(this, this.posX, this.posY + (double) (this.height / 16.0F), this.posZ, event.getRadius(), event.getFire(), true); ++ } ++ // CraftBukkit end + } + + protected void writeEntityToNBT(NBTTagCompound compound) diff --git a/patches/net/minecraft/entity/item/EntityXPOrb.java.patch b/patches/net/minecraft/entity/item/EntityXPOrb.java.patch new file mode 100644 index 00000000..3e8b47c6 --- /dev/null +++ b/patches/net/minecraft/entity/item/EntityXPOrb.java.patch @@ -0,0 +1,133 @@ +--- ../src-base/minecraft/net/minecraft/entity/item/EntityXPOrb.java ++++ ../src-work/minecraft/net/minecraft/entity/item/EntityXPOrb.java +@@ -3,6 +3,7 @@ + import net.minecraft.block.material.Material; + import net.minecraft.enchantment.EnchantmentHelper; + import net.minecraft.entity.Entity; ++import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.MoverType; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.init.Enchantments; +@@ -15,6 +16,9 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; + + public class EntityXPOrb extends Entity + { +@@ -74,6 +78,7 @@ + public void onUpdate() + { + super.onUpdate(); ++ EntityPlayer prevTarget = this.closestPlayer;// CraftBukkit - store old target + + if (this.delayBeforeCanPickup > 0) + { +@@ -117,19 +122,30 @@ + + if (this.closestPlayer != null) + { +- double d1 = (this.closestPlayer.posX - this.posX) / 8.0D; +- double d2 = (this.closestPlayer.posY + (double)this.closestPlayer.getEyeHeight() / 2.0D - this.posY) / 8.0D; +- double d3 = (this.closestPlayer.posZ - this.posZ) / 8.0D; +- double d4 = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3); +- double d5 = 1.0D - d4; ++ // CraftBukkit start ++ boolean cancelled = false; ++ if (this.closestPlayer != prevTarget) { ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this, closestPlayer, EntityTargetEvent.TargetReason.CLOSEST_PLAYER); ++ EntityLivingBase target = event.getTarget() == null ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ closestPlayer = target instanceof EntityPlayer ? (EntityPlayer) target : null; ++ cancelled = event.isCancelled(); ++ } + +- if (d5 > 0.0D) +- { +- d5 = d5 * d5; +- this.motionX += d1 / d4 * d5 * 0.1D; +- this.motionY += d2 / d4 * d5 * 0.1D; +- this.motionZ += d3 / d4 * d5 * 0.1D; ++ if (!cancelled && closestPlayer != null) { ++ double d1 = (this.closestPlayer.posX - this.posX) / 8.0D; ++ double d2 = (this.closestPlayer.posY + (double) this.closestPlayer.getEyeHeight() / 2.0D - this.posY) / 8.0D; ++ double d3 = (this.closestPlayer.posZ - this.posZ) / 8.0D; ++ double d4 = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3); ++ double d5 = 1.0D - d4; ++ ++ if (d5 > 0.0D) { ++ d5 = d5 * d5; ++ this.motionX += d1 / d4 * d5 * 0.1D; ++ this.motionY += d2 / d4 * d5 * 0.1D; ++ this.motionZ += d3 / d4 * d5 * 0.1D; ++ } + } ++ // CraftBukkit end + } + + this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ); +@@ -195,14 +211,14 @@ + { + compound.setShort("Health", (short)this.xpOrbHealth); + compound.setShort("Age", (short)this.xpOrbAge); +- compound.setShort("Value", (short)this.xpValue); ++ compound.setInteger("Value", this.xpValue); // Paper - save as Integer + } + + public void readEntityFromNBT(NBTTagCompound compound) + { + this.xpOrbHealth = compound.getShort("Health"); + this.xpOrbAge = compound.getShort("Age"); +- this.xpValue = compound.getShort("Value"); ++ this.xpValue = compound.getInteger("Value"); // Paper - save as Integer + } + + public void onCollideWithPlayer(EntityPlayer entityIn) +@@ -220,13 +236,17 @@ + { + float ratio = itemstack.getItem().getXpRepairRatio(itemstack); + int i = Math.min(roundAverage(this.xpValue * ratio), itemstack.getItemDamage()); +- this.xpValue -= roundAverage(i / ratio); +- itemstack.setItemDamage(itemstack.getItemDamage() - i); ++ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(entityIn, this, itemstack, i); ++ i = event.getRepairAmount(); ++ if (!event.isCancelled()) { ++ this.xpValue -= roundAverage(i / ratio); ++ itemstack.setItemDamage(itemstack.getItemDamage() - i); ++ } + } + + if (this.xpValue > 0) + { +- entityIn.addExperience(this.xpValue); ++ entityIn.addExperience(CraftEventFactory.callPlayerExpChangeEvent(entityIn, this.xpValue).getAmount()); // CraftBukkit - this.value -> event.getAmount() + } + + this.setDead(); +@@ -296,6 +316,24 @@ + + public static int getXPSplit(int expValue) + { ++ // CraftBukkit start ++ if (expValue > 162670129) return expValue - 100000; ++ if (expValue > 81335063) return 81335063; ++ if (expValue > 40667527) return 40667527; ++ if (expValue > 20333759) return 20333759; ++ if (expValue > 10166857) return 10166857; ++ if (expValue > 5083423) return 5083423; ++ if (expValue > 2541701) return 2541701; ++ if (expValue > 1270849) return 1270849; ++ if (expValue > 635413) return 635413; ++ if (expValue > 317701) return 317701; ++ if (expValue > 158849) return 158849; ++ if (expValue > 79423) return 79423; ++ if (expValue > 39709) return 39709; ++ if (expValue > 19853) return 19853; ++ if (expValue > 9923) return 9923; ++ if (expValue > 4957) return 4957; ++ // CraftBukkit end + if (expValue >= 2477) + { + return 2477; diff --git a/patches/net/minecraft/entity/monster/AbstractSkeleton.java.patch b/patches/net/minecraft/entity/monster/AbstractSkeleton.java.patch new file mode 100644 index 00000000..d26eb74f --- /dev/null +++ b/patches/net/minecraft/entity/monster/AbstractSkeleton.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/AbstractSkeleton.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/AbstractSkeleton.java +@@ -42,6 +42,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityCombustEvent; + + public abstract class AbstractSkeleton extends EntityMob implements IRangedAttackMob + { +@@ -136,7 +137,13 @@ + + if (flag) + { +- this.setFire(8); ++ // this.setFire(8); ++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), 8); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setFire(event.getDuration()); ++ } + } + } + } +@@ -168,7 +175,7 @@ + this.setEquipmentBasedOnDifficulty(difficulty); + this.setEnchantmentBasedOnDifficulty(difficulty); + this.setCombatTask(); +- this.setCanPickUpLoot(this.rand.nextFloat() < 0.55F * difficulty.getClampedAdditionalDifficulty()); ++ this.idkwhyreyoudoingthis(this.rand.nextFloat() < 0.55F * difficulty.getClampedAdditionalDifficulty()); + + if (this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()) + { +@@ -221,8 +228,17 @@ + double d2 = target.posZ - this.posZ; + double d3 = (double)MathHelper.sqrt(d0 * d0 + d2 * d2); + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float)(14 - this.world.getDifficulty().getDifficultyId() * 4)); ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getHeldItemMainhand(), entityarrow, 0.8F); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { ++ world.spawnEntity(entityarrow); ++ } + this.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRNG().nextFloat() * 0.4F + 0.8F)); +- this.world.spawnEntity(entityarrow); ++ // this.world.spawnEntity(entityarrow); // CraftBukkit - moved up + } + + protected EntityArrow getArrow(float p_190726_1_) diff --git a/patches/net/minecraft/entity/monster/EntityCreeper.java.patch b/patches/net/minecraft/entity/monster/EntityCreeper.java.patch new file mode 100644 index 00000000..fc9f83d2 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityCreeper.java.patch @@ -0,0 +1,98 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityCreeper.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityCreeper.java +@@ -36,16 +36,19 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityCreeper extends EntityMob + { + private static final DataParameter STATE = EntityDataManager.createKey(EntityCreeper.class, DataSerializers.VARINT); + private static final DataParameter POWERED = EntityDataManager.createKey(EntityCreeper.class, DataSerializers.BOOLEAN); + private static final DataParameter IGNITED = EntityDataManager.createKey(EntityCreeper.class, DataSerializers.BOOLEAN); ++ private static final DataParameter isIgnitedDW = IGNITED; // Paper OBFHELPER + private int lastActiveTime; + private int timeSinceIgnited; +- private int fuseTime = 30; +- private int explosionRadius = 3; ++ public int fuseTime = 30; ++ public int explosionRadius = 3; + private int droppedSkulls; + + public EntityCreeper(World worldIn) +@@ -184,7 +187,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++// super.onDeath(cause); // CraftBukkit - Moved to end + + if (this.world.getGameRules().getBoolean("doMobLoot")) + { +@@ -201,6 +204,8 @@ + this.entityDropItem(new ItemStack(Items.SKULL, 1, 4), 0.0F); + } + } ++ ++ super.onDeath(cause); // CraftBukkit - Moved from above + } + + public boolean attackEntityAsMob(Entity entityIn) +@@ -237,10 +242,22 @@ + + public void onStruckByLightning(EntityLightningBolt lightningBolt) + { ++ if (lightningBolt == null) ++ lightningBolt = new EntityLightningBolt(this.world, this.posX, this.posY, this.posZ, true); + super.onStruckByLightning(lightningBolt); +- this.dataManager.set(POWERED, Boolean.valueOf(true)); ++ // CraftBukkit start ++ if (CraftEventFactory.callCreeperPowerEvent(this, lightningBolt, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) { ++ return; ++ } ++ ++ this.setPowered(true); + } + ++ public void setPowered(boolean powered) { ++ this.dataManager.set(EntityCreeper.POWERED, powered); ++ } ++ // CraftBukkit end ++ + protected boolean processInteract(EntityPlayer player, EnumHand hand) + { + ItemStack itemstack = player.getHeldItem(hand); +@@ -267,10 +284,19 @@ + { + boolean flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this); + float f = this.getPowered() ? 2.0F : 1.0F; +- this.dead = true; +- this.world.createExplosion(this, this.posX, this.posY, this.posZ, (float)this.explosionRadius * f, flag); +- this.setDead(); +- this.spawnLingeringCloud(); ++ // CraftBukkit start ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.explosionRadius * f, false); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.dead = true; ++ this.world.newExplosion(this, this.posX, this.posY, this.posZ, event.getRadius(), event.getFire(), flag); ++ this.setDead(); ++ this.spawnLingeringCloud(); ++ } else { ++ timeSinceIgnited = 0; ++ this.dataManager.set(isIgnitedDW, Boolean.valueOf(false)); // Paper ++ } ++ // CraftBukkit end + } + } + +@@ -281,6 +307,7 @@ + if (!collection.isEmpty()) + { + EntityAreaEffectCloud entityareaeffectcloud = new EntityAreaEffectCloud(this.world, this.posX, this.posY, this.posZ); ++ entityareaeffectcloud.setOwner(this); + entityareaeffectcloud.setRadius(2.5F); + entityareaeffectcloud.setRadiusOnUse(-0.5F); + entityareaeffectcloud.setWaitTime(10); diff --git a/patches/net/minecraft/entity/monster/EntityEnderman.java.patch b/patches/net/minecraft/entity/monster/EntityEnderman.java.patch new file mode 100644 index 00000000..0daefee9 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityEnderman.java.patch @@ -0,0 +1,81 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityEnderman.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityEnderman.java +@@ -47,6 +47,7 @@ + import net.minecraft.util.math.Vec3d; + import net.minecraft.world.World; + import net.minecraft.world.storage.loot.LootTableList; ++import org.bukkit.event.entity.EntityTargetEvent; + + public class EntityEnderman extends EntityMob + { +@@ -97,25 +98,32 @@ + + public void setAttackTarget(@Nullable EntityLivingBase entitylivingbaseIn) + { +- super.setAttackTarget(entitylivingbaseIn); ++ // CraftBukkit start - fire event ++ setAttackTarget(entitylivingbaseIn, EntityTargetEvent.TargetReason.UNKNOWN, true); ++ } ++ ++ @Override ++ public boolean setAttackTarget(@Nullable EntityLivingBase entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { ++ if (!super.setAttackTarget(entityliving, reason, fireEvent)) { ++ return false; ++ } ++ entityliving = getAttackTarget(); ++ // CraftBukkit end + IAttributeInstance iattributeinstance = this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED); + +- if (entitylivingbaseIn == null) +- { ++ if (entityliving == null) { + this.targetChangeTime = 0; + this.dataManager.set(SCREAMING, Boolean.valueOf(false)); + iattributeinstance.removeModifier(ATTACKING_SPEED_BOOST); +- } +- else +- { ++ } else { + this.targetChangeTime = this.ticksExisted; + this.dataManager.set(SCREAMING, Boolean.valueOf(true)); + +- if (!iattributeinstance.hasModifier(ATTACKING_SPEED_BOOST)) +- { ++ if (!iattributeinstance.hasModifier(ATTACKING_SPEED_BOOST)) { + iattributeinstance.applyModifier(ATTACKING_SPEED_BOOST); + } + } ++ return true; + } + + protected void entityInit() +@@ -524,8 +532,12 @@ + + if (iblockstate2 != null && this.canPlaceBlock(world, blockpos, iblockstate2.getBlock(), iblockstate, iblockstate1) && net.minecraftforge.event.ForgeEventFactory.onBlockPlace(enderman, new net.minecraftforge.common.util.BlockSnapshot(world, blockpos, iblockstate2), net.minecraft.util.EnumFacing.UP).isCanceled()) + { +- world.setBlockState(blockpos, iblockstate2, 3); +- this.enderman.setHeldBlockState((IBlockState)null); ++ // CraftBukkit start - Place event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockpos, this.enderman.getHeldBlockState().getBlock(), this.enderman.getHeldBlockState().getBlock().getMetaFromState(this.enderman.getHeldBlockState())).isCancelled()) { ++ world.setBlockState(blockpos, iblockstate2, 3); ++ this.enderman.setHeldBlockState((IBlockState) null); ++ } ++ // CraftBukkit end + } + } + +@@ -590,8 +602,12 @@ + + if (EntityEnderman.CARRIABLE_BLOCKS.contains(block) && flag) + { +- this.enderman.setHeldBlockState(iblockstate); +- world.setBlockToAir(blockpos); ++ // CraftBukkit start - Pickup event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, this.enderman.world.getWorld().getBlockAt(blockpos.getX(), blockpos.getY(), blockpos.getZ()), org.bukkit.Material.AIR).isCancelled()) { ++ this.enderman.setHeldBlockState(iblockstate); ++ world.setBlockToAir(blockpos); ++ } ++ // CraftBukkit end + } + } + } diff --git a/patches/net/minecraft/entity/monster/EntityGhast.java.patch b/patches/net/minecraft/entity/monster/EntityGhast.java.patch new file mode 100644 index 00000000..1958a7d5 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityGhast.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityGhast.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityGhast.java +@@ -225,7 +225,8 @@ + double d4 = entitylivingbase.posZ - (this.parentEntity.posZ + vec3d.z * 4.0D); + world.playEvent((EntityPlayer)null, 1016, new BlockPos(this.parentEntity), 0); + EntityLargeFireball entitylargefireball = new EntityLargeFireball(world, this.parentEntity, d2, d3, d4); +- entitylargefireball.explosionPower = this.parentEntity.getFireballStrength(); ++ // CraftBukkit - set bukkitYield when setting explosionpower ++ entitylargefireball.bukkitYield = entitylargefireball.explosionPower = this.parentEntity.getFireballStrength(); + entitylargefireball.posX = this.parentEntity.posX + vec3d.x * 4.0D; + entitylargefireball.posY = this.parentEntity.posY + (double)(this.parentEntity.height / 2.0F) + 0.5D; + entitylargefireball.posZ = this.parentEntity.posZ + vec3d.z * 4.0D; diff --git a/patches/net/minecraft/entity/monster/EntityGolem.java.patch b/patches/net/minecraft/entity/monster/EntityGolem.java.patch new file mode 100644 index 00000000..51e88518 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityGolem.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityGolem.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityGolem.java +@@ -41,7 +41,7 @@ + return 120; + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return false; + } diff --git a/patches/net/minecraft/entity/monster/EntityMagmaCube.java.patch b/patches/net/minecraft/entity/monster/EntityMagmaCube.java.patch new file mode 100644 index 00000000..c43d9a5a --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityMagmaCube.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityMagmaCube.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityMagmaCube.java +@@ -44,7 +44,7 @@ + return this.world.checkNoEntityCollision(this.getEntityBoundingBox(), this) && this.world.getCollisionBoxes(this, this.getEntityBoundingBox()).isEmpty() && !this.world.containsAnyLiquid(this.getEntityBoundingBox()); + } + +- protected void setSlimeSize(int size, boolean resetHealth) ++ public void setSlimeSize(int size, boolean resetHealth) + { + super.setSlimeSize(size, resetHealth); + this.getEntityAttribute(SharedMonsterAttributes.ARMOR).setBaseValue((double)(size * 3)); diff --git a/patches/net/minecraft/entity/monster/EntityMob.java.patch b/patches/net/minecraft/entity/monster/EntityMob.java.patch new file mode 100644 index 00000000..985339fd --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityMob.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityMob.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityMob.java +@@ -18,6 +18,7 @@ + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.EnumSkyBlock; + import net.minecraft.world.World; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; + + public abstract class EntityMob extends EntityCreature implements IMob + { +@@ -111,7 +112,15 @@ + + if (j > 0) + { +- entityIn.setFire(j * 4); ++ // entityIn.setFire(j * 4); ++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item ++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entityIn.getBukkitEntity(), j * 4); ++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent); ++ ++ if (!combustEvent.isCancelled()) { ++ entityIn.setFire(combustEvent.getDuration()); ++ } ++ // CraftBukkit end + } + + if (entityIn instanceof EntityPlayer) +@@ -153,17 +162,18 @@ + } + else + { +- int i = this.world.getLightFromNeighbors(blockpos); +- ++ boolean passes; // Paper + if (this.world.isThundering()) + { + int j = this.world.getSkylightSubtracted(); + this.world.setSkylightSubtracted(10); +- i = this.world.getLightFromNeighbors(blockpos); ++ passes = !world.isLightLevel(blockpos, this.rand.nextInt(9)); // Paper + this.world.setSkylightSubtracted(j); +- } ++ }else{ ++ passes = !world.isLightLevel(blockpos, this.rand.nextInt(9)); ++ } // Paper + +- return i <= this.rand.nextInt(8); ++ return passes; // Paper + } + } + diff --git a/patches/net/minecraft/entity/monster/EntityPigZombie.java.patch b/patches/net/minecraft/entity/monster/EntityPigZombie.java.patch new file mode 100644 index 00000000..30f5faea --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityPigZombie.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityPigZombie.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityPigZombie.java +@@ -31,7 +31,7 @@ + { + private static final UUID ATTACK_SPEED_BOOST_MODIFIER_UUID = UUID.fromString("49455A49-7EC5-45BA-B886-3B90B23A1718"); + private static final AttributeModifier ATTACK_SPEED_BOOST_MODIFIER = (new AttributeModifier(ATTACK_SPEED_BOOST_MODIFIER_UUID, "Attacking speed boost", 0.05D, 0)).setSaved(false); +- private int angerLevel; ++ public int angerLevel; + private int randomSoundDelay; + private UUID angerTargetUUID; + diff --git a/patches/net/minecraft/entity/monster/EntityShulker.java.patch b/patches/net/minecraft/entity/monster/EntityShulker.java.patch new file mode 100644 index 00000000..77e82c53 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityShulker.java.patch @@ -0,0 +1,39 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityShulker.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityShulker.java +@@ -47,6 +47,8 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.event.entity.EntityTeleportEvent; + + public class EntityShulker extends EntityGolem implements IMob + { +@@ -55,7 +57,7 @@ + protected static final DataParameter ATTACHED_FACE = EntityDataManager.createKey(EntityShulker.class, DataSerializers.FACING); + protected static final DataParameter> ATTACHED_BLOCK_POS = EntityDataManager.>createKey(EntityShulker.class, DataSerializers.OPTIONAL_BLOCK_POS); + protected static final DataParameter PEEK_TICK = EntityDataManager.createKey(EntityShulker.class, DataSerializers.BYTE); +- protected static final DataParameter COLOR = EntityDataManager.createKey(EntityShulker.class, DataSerializers.BYTE); ++ public static final DataParameter COLOR = EntityDataManager.createKey(EntityShulker.class, DataSerializers.BYTE); + public static final EnumDyeColor DEFAULT_COLOR = EnumDyeColor.PURPLE; + private float prevPeekAmount; + private float peekAmount; +@@ -419,9 +421,15 @@ + { + if (this.world.isBlockNormalCube(blockpos1.offset(enumfacing), false)) + { +- this.dataManager.set(ATTACHED_FACE, enumfacing); +- flag = true; +- break; ++ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), new Location(this.world.getWorld(), blockpos1.getX(), blockpos1.getY(), blockpos1.getZ())); ++ this.world.getServer().getPluginManager().callEvent(teleport); ++ if (!teleport.isCancelled()) { ++ Location to = teleport.getTo(); ++ blockpos1 = new BlockPos(to.getX(), to.getY(), to.getZ()); ++ this.dataManager.set(ATTACHED_FACE, enumfacing); ++ flag = true; ++ break; ++ } + } + } + diff --git a/patches/net/minecraft/entity/monster/EntitySilverfish.java.patch b/patches/net/minecraft/entity/monster/EntitySilverfish.java.patch new file mode 100644 index 00000000..8f92fee8 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySilverfish.java.patch @@ -0,0 +1,22 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySilverfish.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySilverfish.java +@@ -221,6 +221,9 @@ + + if (BlockSilverfish.canContainSilverfish(iblockstate)) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.entity, blockpos, Blocks.MONSTER_EGG, Block.getIdFromBlock(BlockSilverfish.getBlockById(iblockstate.getBlock().getMetaFromState(iblockstate)))).isCancelled()) { ++ return; ++ } + world.setBlockState(blockpos, Blocks.MONSTER_EGG.getDefaultState().withProperty(BlockSilverfish.VARIANT, BlockSilverfish.EnumType.forModelBlock(iblockstate)), 3); + this.entity.spawnExplosionParticle(); + this.entity.setDead(); +@@ -273,6 +276,9 @@ + + if (iblockstate.getBlock() == Blocks.MONSTER_EGG) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockpos1, Blocks.AIR, 0).isCancelled()) { ++ continue; ++ } + if (net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(world, this.silverfish)) + { + world.destroyBlock(blockpos1, true); diff --git a/patches/net/minecraft/entity/monster/EntitySkeleton.java.patch b/patches/net/minecraft/entity/monster/EntitySkeleton.java.patch new file mode 100644 index 00000000..9386efaf --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySkeleton.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySkeleton.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySkeleton.java +@@ -56,7 +56,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // CraftBukkit - moved down + + if (cause.getTrueSource() instanceof EntityCreeper) + { +@@ -68,6 +68,7 @@ + this.entityDropItem(new ItemStack(Items.SKULL, 1, 0), 0.0F); + } + } ++ super.onDeath(cause); // CraftBukkit - moved from above + } + + protected EntityArrow getArrow(float p_190726_1_) diff --git a/patches/net/minecraft/entity/monster/EntitySlime.java.patch b/patches/net/minecraft/entity/monster/EntitySlime.java.patch new file mode 100644 index 00000000..35ce0afa --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySlime.java.patch @@ -0,0 +1,54 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySlime.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySlime.java +@@ -35,6 +35,7 @@ + import net.minecraft.world.biome.Biome; + import net.minecraft.world.chunk.Chunk; + import net.minecraft.world.storage.loot.LootTableList; ++import org.bukkit.event.entity.SlimeSplitEvent; + + public class EntitySlime extends EntityLiving implements IMob + { +@@ -66,7 +67,7 @@ + this.dataManager.register(SLIME_SIZE, Integer.valueOf(1)); + } + +- protected void setSlimeSize(int size, boolean resetHealth) ++ public void setSlimeSize(int size, boolean resetHealth) + { + this.dataManager.set(SLIME_SIZE, Integer.valueOf(size)); + this.setSize(0.51000005F * (float)size, 0.51000005F * (float)size); +@@ -204,6 +205,16 @@ + { + int j = 2 + this.rand.nextInt(3); + ++ SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), j); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled() && event.getCount() > 0) { ++ j = event.getCount(); ++ } else { ++ super.setDead(); ++ return; ++ } ++ + for (int k = 0; k < j; ++k) + { + float f = ((float)(k % 2) - 0.5F) * (float)i / 4.0F; +@@ -222,7 +233,7 @@ + + entityslime.setSlimeSize(i / 2, true); + entityslime.setLocationAndAngles(this.posX + (double)f, this.posY + 0.5D, this.posZ + (double)f1, this.rand.nextFloat() * 360.0F, 0.0F); +- this.world.spawnEntity(entityslime); ++ this.world.spawnEntity(entityslime, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); + } + } + +@@ -319,7 +330,7 @@ + return super.getCanSpawnHere(); + } + +- if (this.rand.nextInt(10) == 0 && chunk.getRandomWithSeed(987234911L).nextInt(10) == 0 && this.posY < 40.0D) ++ if (this.rand.nextInt(10) == 0 && chunk.getRandomWithSeed(world.spigotConfig.slimeSeed).nextInt(10) == 0 && this.posY < 40.0D) + { + return super.getCanSpawnHere(); + } diff --git a/patches/net/minecraft/entity/monster/EntitySnowman.java.patch b/patches/net/minecraft/entity/monster/EntitySnowman.java.patch new file mode 100644 index 00000000..e56ed500 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySnowman.java.patch @@ -0,0 +1,46 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySnowman.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySnowman.java +@@ -30,6 +30,8 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; + import net.minecraft.world.storage.loot.LootTableList; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.player.PlayerShearEntityEvent; + + public class EntitySnowman extends EntityGolem implements IRangedAttackMob, net.minecraftforge.common.IShearable + { +@@ -101,7 +103,9 @@ + + if (this.world.getBiome(new BlockPos(i, 0, k)).getTemperature(new BlockPos(i, j, k)) > 1.0F) + { +- this.attackEntityFrom(DamageSource.ON_FIRE, 1.0F); ++ // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING ++ // this.attackEntityFrom(DamageSource.ON_FIRE, 1.0F); ++ this.attackEntityFrom(CraftEventFactory.MELTING, 1.0F); + } + + if (!net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this)) +@@ -118,7 +122,7 @@ + + if (this.world.getBlockState(blockpos).getMaterial() == Material.AIR && this.world.getBiome(blockpos).getTemperature(blockpos) < 0.8F && Blocks.SNOW_LAYER.canPlaceBlockAt(this.world, blockpos)) + { +- this.world.setBlockState(blockpos, Blocks.SNOW_LAYER.getDefaultState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.world, blockpos, Blocks.SNOW_LAYER.getDefaultState(), this); + } + } + } +@@ -154,6 +158,14 @@ + + if (false && itemstack.getItem() == Items.SHEARS && this.isPumpkinEquipped() && !this.world.isRemote) //Forge: Moved to onSheared + { ++ // CraftBukkit start ++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((org.bukkit.entity.Player) player.getBukkitEntity(), this.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end ++ + this.setPumpkinEquipped(false); + itemstack.damageItem(1, player); + } diff --git a/patches/net/minecraft/entity/monster/EntitySpellcasterIllager.java.patch b/patches/net/minecraft/entity/monster/EntitySpellcasterIllager.java.patch new file mode 100644 index 00000000..32f07c32 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySpellcasterIllager.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySpellcasterIllager.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySpellcasterIllager.java +@@ -66,7 +66,7 @@ + this.dataManager.set(SPELL, Byte.valueOf((byte)spellType.id)); + } + +- protected EntitySpellcasterIllager.SpellType getSpellType() ++ public SpellType getSpellType() + { + return !this.world.isRemote ? this.activeSpell : EntitySpellcasterIllager.SpellType.getFromId(((Byte)this.dataManager.get(SPELL)).byteValue()); + } diff --git a/patches/net/minecraft/entity/monster/EntitySpider.java.patch b/patches/net/minecraft/entity/monster/EntitySpider.java.patch new file mode 100644 index 00000000..defcba68 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntitySpider.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntitySpider.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntitySpider.java +@@ -179,7 +179,8 @@ + EntitySkeleton entityskeleton = new EntitySkeleton(this.world); + entityskeleton.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F); + entityskeleton.onInitialSpawn(difficulty, (IEntityLivingData)null); +- this.world.spawnEntity(entityskeleton); ++ // this.world.spawnEntity(entityskeleton); ++ this.world.spawnEntity(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); + entityskeleton.startRiding(this); + } + diff --git a/patches/net/minecraft/entity/monster/EntityVex.java.patch b/patches/net/minecraft/entity/monster/EntityVex.java.patch new file mode 100644 index 00000000..5a05d118 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityVex.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityVex.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityVex.java +@@ -35,6 +35,7 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityTargetEvent; + + public class EntityVex extends EntityMob + { +@@ -316,7 +317,8 @@ + + public void startExecuting() + { +- EntityVex.this.setAttackTarget(EntityVex.this.owner.getAttackTarget()); ++ // EntityVex.this.setAttackTarget(EntityVex.this.owner.getAttackTarget()); ++ EntityVex.this.setAttackTarget(EntityVex.this.owner.getAttackTarget(), EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); + super.startExecuting(); + } + } diff --git a/patches/net/minecraft/entity/monster/EntityWitherSkeleton.java.patch b/patches/net/minecraft/entity/monster/EntityWitherSkeleton.java.patch new file mode 100644 index 00000000..c6d1cc14 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityWitherSkeleton.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityWitherSkeleton.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityWitherSkeleton.java +@@ -63,7 +63,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // CraftBukkit - down + + if (cause.getTrueSource() instanceof EntityCreeper) + { +@@ -75,6 +75,8 @@ + this.entityDropItem(new ItemStack(Items.SKULL, 1, 1), 0.0F); + } + } ++ ++ super.onDeath(cause); // CraftBukkit - moved from above + } + + protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty) diff --git a/patches/net/minecraft/entity/monster/EntityZombie.java.patch b/patches/net/minecraft/entity/monster/EntityZombie.java.patch new file mode 100644 index 00000000..be13ba09 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityZombie.java.patch @@ -0,0 +1,137 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityZombie.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityZombie.java +@@ -51,6 +51,10 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++import org.bukkit.event.entity.EntityTargetEvent; + + public class EntityZombie extends EntityMob + { +@@ -87,7 +91,9 @@ + this.tasks.addTask(6, new EntityAIMoveThroughVillage(this, 1.0D, false)); + this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true, new Class[] {EntityPigZombie.class})); + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityPlayer.class, true)); ++ if (this.world.spigotConfig.zombieAggressiveTowardsVillager) { + this.targetTasks.addTask(3, new EntityAINearestAttackableTarget(this, EntityVillager.class, false)); ++ } + this.targetTasks.addTask(3, new EntityAINearestAttackableTarget(this, EntityIronGolem.class, true)); + } + +@@ -215,7 +221,13 @@ + + if (flag) + { +- this.setFire(8); ++ // this.setFire(8); ++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), 8); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setFire(event.getDuration()); ++ } + } + } + } +@@ -264,14 +276,14 @@ + int j1 = j + MathHelper.getInt(this.rand, 7, 40) * MathHelper.getInt(this.rand, -1, 1); + int k1 = k + MathHelper.getInt(this.rand, 7, 40) * MathHelper.getInt(this.rand, -1, 1); + +- if (this.world.getBlockState(new BlockPos(i1, j1 - 1, k1)).isSideSolid(this.world, new BlockPos(i1, j1 - 1, k1), net.minecraft.util.EnumFacing.UP) && this.world.getLightFromNeighbors(new BlockPos(i1, j1, k1)) < 10) +- { ++ if (this.world.getBlockState(new BlockPos(i1, j1 - 1, k1)).isSideSolid(this.world, new BlockPos(i1, j1 - 1, k1), net.minecraft.util.EnumFacing.UP) && !this.world.isLightLevel(new BlockPos(i1, j1, k1), 10)) { // Paper ++ + entityzombie.setPosition((double)i1, (double)j1, (double)k1); + + if (!this.world.isAnyPlayerWithinRangeAt((double)i1, (double)j1, (double)k1, 7.0D) && this.world.checkNoEntityCollision(entityzombie.getEntityBoundingBox(), entityzombie) && this.world.getCollisionBoxes(entityzombie, entityzombie.getEntityBoundingBox()).isEmpty() && !this.world.containsAnyLiquid(entityzombie.getEntityBoundingBox())) + { +- this.world.spawnEntity(entityzombie); +- if (entitylivingbase != null) entityzombie.setAttackTarget(entitylivingbase); ++ this.world.spawnEntity(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); ++ if (entitylivingbase != null) entityzombie.setAttackTarget(entitylivingbase, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); + entityzombie.onInitialSpawn(this.world.getDifficultyForLocation(new BlockPos(entityzombie)), (IEntityLivingData)null); + this.getEntityAttribute(SPAWN_REINFORCEMENTS_CHANCE).applyModifier(new AttributeModifier("Zombie reinforcement caller charge", -0.05000000074505806D, 0)); + entityzombie.getEntityAttribute(SPAWN_REINFORCEMENTS_CHANCE).applyModifier(new AttributeModifier("Zombie reinforcement callee charge", -0.05000000074505806D, 0)); +@@ -299,7 +311,13 @@ + + if (this.getHeldItemMainhand().isEmpty() && this.isBurning() && this.rand.nextFloat() < f * 0.3F) + { +- entityIn.setFire(2 * (int)f); ++ // entityIn.setFire(2 * (int)f); ++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), entityIn.getBukkitEntity(), 2 * (int) f); // PAIL: fixme ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ entityIn.setFire(event.getDuration()); ++ } + } + } + +@@ -405,7 +423,7 @@ + EntityZombieVillager entityzombievillager = new EntityZombieVillager(this.world); + entityzombievillager.copyLocationAndAnglesFrom(entityvillager); + this.world.removeEntity(entityvillager); +- entityzombievillager.onInitialSpawn(this.world.getDifficultyForLocation(new BlockPos(entityzombievillager)), new EntityZombie.GroupData(false)); ++ entityzombievillager.onInitialSpawn(this.world.getDifficultyForLocation(new BlockPos(entityzombievillager)), new GroupData(false)); + entityzombievillager.setProfession(entityvillager.getProfession()); + entityzombievillager.setChild(entityvillager.isChild()); + entityzombievillager.setNoAI(entityvillager.isAIDisabled()); +@@ -416,7 +434,7 @@ + entityzombievillager.setAlwaysRenderNameTag(entityvillager.getAlwaysRenderNameTag()); + } + +- this.world.spawnEntity(entityzombievillager); ++ this.world.spawnEntity(entityzombievillager, CreatureSpawnEvent.SpawnReason.INFECTION); + this.world.playEvent((EntityPlayer)null, 1026, new BlockPos(this), 0); + } + } +@@ -443,16 +461,16 @@ + { + livingdata = super.onInitialSpawn(difficulty, livingdata); + float f = difficulty.getClampedAdditionalDifficulty(); +- this.setCanPickUpLoot(this.rand.nextFloat() < 0.55F * f); ++ this.idkwhyreyoudoingthis(this.rand.nextFloat() < 0.55F * f); + + if (livingdata == null) + { +- livingdata = new EntityZombie.GroupData(this.world.rand.nextFloat() < net.minecraftforge.common.ForgeModContainer.zombieBabyChance); ++ livingdata = new GroupData(this.world.rand.nextFloat() < net.minecraftforge.common.ForgeModContainer.zombieBabyChance); + } + +- if (livingdata instanceof EntityZombie.GroupData) ++ if (livingdata instanceof GroupData) + { +- EntityZombie.GroupData entityzombie$groupdata = (EntityZombie.GroupData)livingdata; ++ GroupData entityzombie$groupdata = (GroupData)livingdata; + + if (entityzombie$groupdata.isChild) + { +@@ -475,7 +493,7 @@ + entitychicken1.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F); + entitychicken1.onInitialSpawn(difficulty, (IEntityLivingData)null); + entitychicken1.setChickenJockey(true); +- this.world.spawnEntity(entitychicken1); ++ this.world.spawnEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); + this.startRiding(entitychicken1); + } + } +@@ -543,7 +561,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // CraftBukkit - moved down + + if (cause.getTrueSource() instanceof EntityCreeper) + { +@@ -560,6 +578,7 @@ + } + } + } ++ super.onDeath(cause); + } + + protected ItemStack getSkullDrop() diff --git a/patches/net/minecraft/entity/monster/EntityZombieVillager.java.patch b/patches/net/minecraft/entity/monster/EntityZombieVillager.java.patch new file mode 100644 index 00000000..3acf2c28 --- /dev/null +++ b/patches/net/minecraft/entity/monster/EntityZombieVillager.java.patch @@ -0,0 +1,49 @@ +--- ../src-base/minecraft/net/minecraft/entity/monster/EntityZombieVillager.java ++++ ../src-work/minecraft/net/minecraft/entity/monster/EntityZombieVillager.java +@@ -19,6 +19,7 @@ + import net.minecraft.network.datasync.DataSerializers; + import net.minecraft.network.datasync.EntityDataManager; + import net.minecraft.potion.PotionEffect; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.DamageSource; + import net.minecraft.util.EnumHand; + import net.minecraft.util.ResourceLocation; +@@ -38,6 +39,8 @@ + private int conversionTime; + private UUID converstionStarter; + ++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field ++ + public EntityZombieVillager(World worldIn) + { + super(worldIn); +@@ -110,6 +113,11 @@ + if (!this.world.isRemote && this.isConverting()) + { + int i = this.getConversionProgress(); ++ // CraftBukkit start - Use wall time instead of ticks for villager conversion ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ this.lastTick = MinecraftServer.currentTick; ++ i *= elapsedTicks; ++ // CraftBukkit end + this.conversionTime -= i; + + if (this.conversionTime <= 0) +@@ -145,7 +153,7 @@ + } + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return !this.isConverting(); + } +@@ -203,7 +211,7 @@ + entityvillager.setAlwaysRenderNameTag(this.getAlwaysRenderNameTag()); + } + +- this.world.spawnEntity(entityvillager); ++ this.world.spawnEntity(entityvillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED); + + if (this.converstionStarter != null) + { diff --git a/patches/net/minecraft/entity/passive/AbstractChestHorse.java.patch b/patches/net/minecraft/entity/passive/AbstractChestHorse.java.patch new file mode 100644 index 00000000..22ca5335 --- /dev/null +++ b/patches/net/minecraft/entity/passive/AbstractChestHorse.java.patch @@ -0,0 +1,23 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/AbstractChestHorse.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/AbstractChestHorse.java +@@ -72,7 +72,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // CraftBukkit - moved down + + if (this.hasChest()) + { +@@ -81,8 +81,10 @@ + this.dropItem(Item.getItemFromBlock(Blocks.CHEST), 1); + } + +- this.setChested(false); ++ // this.setChested(false); // CraftBukkit - moved down + } ++ super.onDeath(cause); ++ this.setChested(false); + } + + public static void registerFixesAbstractChestHorse(DataFixer fixer, Class entityClass) diff --git a/patches/net/minecraft/entity/passive/AbstractHorse.java.patch b/patches/net/minecraft/entity/passive/AbstractHorse.java.patch new file mode 100644 index 00000000..72b72329 --- /dev/null +++ b/patches/net/minecraft/entity/passive/AbstractHorse.java.patch @@ -0,0 +1,132 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/AbstractHorse.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/AbstractHorse.java +@@ -56,6 +56,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityRegainHealthEvent; + + public abstract class AbstractHorse extends EntityAnimal implements IInventoryChangedListener, IJumpingMount + { +@@ -66,7 +67,7 @@ + return p_apply_1_ instanceof AbstractHorse && ((AbstractHorse)p_apply_1_).isBreeding(); + } + }; +- protected static final IAttribute JUMP_STRENGTH = (new RangedAttribute((IAttribute)null, "horse.jumpStrength", 0.7D, 0.0D, 2.0D)).setDescription("Jump Strength").setShouldWatch(true); ++ public static final IAttribute JUMP_STRENGTH = (new RangedAttribute((IAttribute)null, "horse.jumpStrength", 0.7D, 0.0D, 2.0D)).setDescription("Jump Strength").setShouldWatch(true); + private static final DataParameter STATUS = EntityDataManager.createKey(AbstractHorse.class, DataSerializers.BYTE); + private static final DataParameter> OWNER_UNIQUE_ID = EntityDataManager.>createKey(AbstractHorse.class, DataSerializers.OPTIONAL_UNIQUE_ID); + private int eatingCounter; +@@ -75,7 +76,7 @@ + public int tailCounter; + public int sprintCounter; + protected boolean horseJumping; +- protected ContainerHorseChest horseChest; ++ public ContainerHorseChest horseChest; + protected int temper; + protected float jumpPower; + private boolean allowStandSliding; +@@ -88,6 +89,8 @@ + protected boolean canGallop = true; + protected int gallopTime; + ++ public int maxDomestication = 100; // CraftBukkit - store max domestication value ++ + public AbstractHorse(World worldIn) + { + super(worldIn); +@@ -288,10 +291,10 @@ + return 2; + } + +- protected void initHorseChest() ++ public void initHorseChest() + { + ContainerHorseChest containerhorsechest = this.horseChest; +- this.horseChest = new ContainerHorseChest("HorseChest", this.getInventorySize()); ++ this.horseChest = new ContainerHorseChest("HorseChest", this.getInventorySize(), this); + this.horseChest.setCustomName(this.getName()); + + if (containerhorsechest != null) +@@ -465,7 +468,7 @@ + + public int getMaxTemper() + { +- return 100; ++ return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100 + } + + protected float getSoundVolume() +@@ -545,7 +548,7 @@ + + if (this.getHealth() < this.getMaxHealth() && f > 0.0F) + { +- this.heal(f); ++ this.heal(f, EntityRegainHealthEvent.RegainReason.EATING); + flag = true; + } + +@@ -609,7 +612,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // Moved down + + if (!this.world.isRemote && this.horseChest != null) + { +@@ -623,6 +626,7 @@ + } + } + } ++ super.onDeath(cause); + } + + public void onLivingUpdate() +@@ -638,7 +642,7 @@ + { + if (this.rand.nextInt(900) == 0 && this.deathTime == 0) + { +- this.heal(1.0F); ++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); + } + + if (this.canEatGrass()) +@@ -941,7 +945,7 @@ + { + compound.setString("OwnerUUID", this.getOwnerUniqueId().toString()); + } +- ++ compound.setInteger("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit + if (!this.horseChest.getStackInSlot(0).isEmpty()) + { + compound.setTag("SaddleItem", this.horseChest.getStackInSlot(0).writeToNBT(new NBTTagCompound())); +@@ -971,6 +975,9 @@ + { + this.setOwnerUniqueId(UUID.fromString(s)); + } ++ if (compound.hasKey("Bukkit.MaxDomestication")) { ++ this.maxDomestication = compound.getInteger("Bukkit.MaxDomestication"); ++ } + + IAttributeInstance iattributeinstance = this.getAttributeMap().getAttributeInstanceByName("Speed"); + +@@ -1074,6 +1081,18 @@ + + public void handleStartJump(int p_184775_1_) + { ++ // CraftBukkit start ++ float power; ++ if (p_184775_1_ >= 90) { ++ power = 1.0F; ++ } else { ++ power = 0.4F + 0.4F * (float) p_184775_1_ / 90.0F; ++ } ++ org.bukkit.event.entity.HorseJumpEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callHorseJumpEvent(this, power); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.allowStandSliding = true; + this.makeHorseRear(); + } diff --git a/patches/net/minecraft/entity/passive/EntityAnimal.java.patch b/patches/net/minecraft/entity/passive/EntityAnimal.java.patch new file mode 100644 index 00000000..79701299 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityAnimal.java.patch @@ -0,0 +1,45 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityAnimal.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityAnimal.java +@@ -25,6 +25,8 @@ + private int inLove; + private UUID playerInLove; + ++ public ItemStack breedItem; // CraftBukkit - Add breedItem variable ++ + public EntityAnimal(World worldIn) + { + super(worldIn); +@@ -63,6 +65,8 @@ + } + } + ++ /* CraftBukkit start ++ // Function disabled as it has no special function anymore after setSitting is disabled. + public boolean attackEntityFrom(DamageSource source, float amount) + { + if (this.isEntityInvulnerable(source)) +@@ -75,6 +79,7 @@ + return super.attackEntityFrom(source, amount); + } + } ++ // CraftBukkit end */ + + public float getBlockPathWeight(BlockPos pos) + { +@@ -118,7 +123,7 @@ + return 120; + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return false; + } +@@ -172,6 +177,7 @@ + if (player != null) + { + this.playerInLove = player.getUniqueID(); ++ this.breedItem = player.inventory.getCurrentItem(); // CraftBukkit + } + + this.world.setEntityState(this, (byte)18); diff --git a/patches/net/minecraft/entity/passive/EntityChicken.java.patch b/patches/net/minecraft/entity/passive/EntityChicken.java.patch new file mode 100644 index 00000000..31bf55aa --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityChicken.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityChicken.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityChicken.java +@@ -174,7 +174,7 @@ + compound.setInteger("EggLayTime", this.timeUntilNextEgg); + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return this.isChickenJockey() && !this.isBeingRidden(); + } diff --git a/patches/net/minecraft/entity/passive/EntityCow.java.patch b/patches/net/minecraft/entity/passive/EntityCow.java.patch new file mode 100644 index 00000000..158873f1 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityCow.java.patch @@ -0,0 +1,39 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityCow.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityCow.java +@@ -25,6 +25,8 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; + import net.minecraft.world.storage.loot.LootTableList; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + + public class EntityCow extends EntityAnimal + { +@@ -95,16 +97,24 @@ + + if (itemstack.getItem() == Items.BUCKET && !player.capabilities.isCreativeMode && !this.isChild()) + { ++ org.bukkit.Location loc = this.getBukkitEntity().getLocation(); ++ org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(player, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), null, itemstack, Items.MILK_BUCKET); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ ItemStack result = CraftItemStack.asNMSCopy(event.getItemStack()); ++ + player.playSound(SoundEvents.ENTITY_COW_MILK, 1.0F, 1.0F); + itemstack.shrink(1); + + if (itemstack.isEmpty()) + { +- player.setHeldItem(hand, new ItemStack(Items.MILK_BUCKET)); ++ player.setHeldItem(hand, result); + } +- else if (!player.inventory.addItemStackToInventory(new ItemStack(Items.MILK_BUCKET))) ++ else if (!player.inventory.addItemStackToInventory(result)) + { +- player.dropItem(new ItemStack(Items.MILK_BUCKET), false); ++ player.dropItem(result, false); + } + + return true; diff --git a/patches/net/minecraft/entity/passive/EntityLlama.java.patch b/patches/net/minecraft/entity/passive/EntityLlama.java.patch new file mode 100644 index 00000000..5c835f14 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityLlama.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityLlama.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityLlama.java +@@ -66,7 +66,7 @@ + this.setSize(0.9F, 1.87F); + } + +- private void setStrength(int strengthIn) ++ public void setStrength(int strengthIn) + { + this.dataManager.set(DATA_STRENGTH_ID, Integer.valueOf(Math.max(1, Math.min(5, strengthIn)))); + } diff --git a/patches/net/minecraft/entity/passive/EntityOcelot.java.patch b/patches/net/minecraft/entity/passive/EntityOcelot.java.patch new file mode 100644 index 00000000..c7dc89ce --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityOcelot.java.patch @@ -0,0 +1,52 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityOcelot.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityOcelot.java +@@ -3,7 +3,6 @@ + import com.google.common.base.Predicate; + import javax.annotation.Nullable; + import net.minecraft.block.Block; +-import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityAgeable; +@@ -106,9 +105,10 @@ + } + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { +- return !this.isTamed() && this.ticksExisted > 2400; ++ return !this.isTamed() /*&& this.ticksExisted > 2400*/; // CraftBukkit ++ + } + + protected void applyEntityAttributes() +@@ -189,7 +189,8 @@ + { + if (this.aiSit != null) + { +- this.aiSit.setSitting(false); ++ // CraftBukkit - moved into EntityLiving.damageEntity(DamageSource, float) ++ // this.aiSit.setSitting(false); + } + + return super.attackEntityFrom(source, amount); +@@ -222,7 +223,8 @@ + + if (!this.world.isRemote) + { +- if (this.rand.nextInt(3) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player)) ++ // CraftBukkit - added event call and isCancelled check ++ if (this.rand.nextInt(3) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) + { + this.setTamedBy(player); + this.setTameSkin(1 + this.world.rand.nextInt(3)); +@@ -368,7 +370,7 @@ + EntityOcelot entityocelot = new EntityOcelot(this.world); + entityocelot.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F); + entityocelot.setGrowingAge(-24000); +- this.world.spawnEntity(entityocelot); ++ this.world.spawnEntity(entityocelot, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OCELOT_BABY); + } + } + diff --git a/patches/net/minecraft/entity/passive/EntityParrot.java.patch b/patches/net/minecraft/entity/passive/EntityParrot.java.patch new file mode 100644 index 00000000..d0d37a02 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityParrot.java.patch @@ -0,0 +1,38 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityParrot.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityParrot.java +@@ -3,8 +3,6 @@ + import com.google.common.base.Predicate; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +-import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import java.util.ArrayList; + import java.util.List; + import java.util.Random; +@@ -16,7 +14,6 @@ + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityAgeable; +-import net.minecraft.entity.EntityList; + import net.minecraft.entity.EntityLiving; + import net.minecraft.entity.IEntityLivingData; + import net.minecraft.entity.SharedMonsterAttributes; +@@ -247,7 +244,7 @@ + + if (!this.world.isRemote) + { +- if (this.rand.nextInt(10) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player)) ++ if (this.rand.nextInt(10) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) // CraftBukkit + { + this.setTamedBy(player); + this.playTameEffect(true); +@@ -420,7 +417,8 @@ + { + if (this.aiSit != null) + { +- this.aiSit.setSitting(false); ++ // CraftBukkit - moved into EntityLiving.damageEntity(DamageSource, float) ++ // this.aiSit.setSitting(false); + } + + return super.attackEntityFrom(source, amount); diff --git a/patches/net/minecraft/entity/passive/EntityPig.java.patch b/patches/net/minecraft/entity/passive/EntityPig.java.patch new file mode 100644 index 00000000..6b2dd013 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityPig.java.patch @@ -0,0 +1,59 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityPig.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityPig.java +@@ -37,6 +37,7 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; + import net.minecraft.world.storage.loot.LootTableList; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntityPig extends EntityAnimal + { +@@ -188,7 +189,7 @@ + + public void onDeath(DamageSource cause) + { +- super.onDeath(cause); ++ // super.onDeath(cause); // CraftBukkit - Moved to end + + if (!this.world.isRemote) + { +@@ -197,6 +198,7 @@ + this.dropItem(Items.SADDLE, 1); + } + } ++ super.onDeath(cause); // CraftBukkit - Moved from above + } + + @Nullable +@@ -222,11 +224,20 @@ + } + } + +- public void onStruckByLightning(EntityLightningBolt lightningBolt) ++ public void onStruckByLightning(@Nullable EntityLightningBolt lightningBolt) + { + if (!this.world.isRemote && !this.isDead) + { ++ if (lightningBolt == null) ++ lightningBolt = new EntityLightningBolt(this.world, this.posX, this.posY, this.posZ, false); + EntityPigZombie entitypigzombie = new EntityPigZombie(this.world); ++ ++ // CraftBukkit start ++ if (CraftEventFactory.callPigZapEvent(this, lightningBolt, entitypigzombie).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ + entitypigzombie.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.GOLDEN_SWORD)); + entitypigzombie.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch); + entitypigzombie.setNoAI(this.isAIDisabled()); +@@ -237,7 +248,8 @@ + entitypigzombie.setAlwaysRenderNameTag(this.getAlwaysRenderNameTag()); + } + +- this.world.spawnEntity(entitypigzombie); ++ // CraftBukkit - added a reason for spawning this creature ++ this.world.spawnEntity(entitypigzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); + this.setDead(); + } + } diff --git a/patches/net/minecraft/entity/passive/EntityRabbit.java.patch b/patches/net/minecraft/entity/passive/EntityRabbit.java.patch new file mode 100644 index 00000000..970ee403 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityRabbit.java.patch @@ -0,0 +1,39 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityRabbit.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityRabbit.java +@@ -68,8 +68,14 @@ + this.setSize(0.4F, 0.5F); + this.jumpHelper = new EntityRabbit.RabbitJumpHelper(this); + this.moveHelper = new EntityRabbit.RabbitMoveHelper(this); ++ this.initializePathFinderGoals(); // CraftBukkit - moved code ++ } ++ ++ // CraftBukkit start - code from constructor ++ public void initializePathFinderGoals(){ + this.setMovementSpeed(0.0D); + } ++ // CraftBukkit end + + protected void initEntityAI() + { +@@ -587,11 +593,21 @@ + + if (integer.intValue() == 0) + { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockpos, Blocks.AIR, 0).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlockState(blockpos, Blocks.AIR.getDefaultState(), 2); + world.destroyBlock(blockpos, true); + } + else + { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockpos, block, block.getMetaFromState(iblockstate.withProperty(BlockCarrot.AGE, Integer.valueOf(integer.intValue() - 1)))).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlockState(blockpos, iblockstate.withProperty(BlockCarrot.AGE, Integer.valueOf(integer.intValue() - 1)), 2); + world.playEvent(2001, blockpos, Block.getStateId(iblockstate)); + } diff --git a/patches/net/minecraft/entity/passive/EntitySheep.java.patch b/patches/net/minecraft/entity/passive/EntitySheep.java.patch new file mode 100644 index 00000000..43a1d85f --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntitySheep.java.patch @@ -0,0 +1,69 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntitySheep.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntitySheep.java +@@ -24,6 +24,7 @@ + import net.minecraft.init.Items; + import net.minecraft.init.SoundEvents; + import net.minecraft.inventory.Container; ++import net.minecraft.inventory.InventoryCraftResult; + import net.minecraft.inventory.InventoryCrafting; + import net.minecraft.item.EnumDyeColor; + import net.minecraft.item.Item; +@@ -45,6 +46,9 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.SheepRegrowWoolEvent; ++import org.bukkit.event.player.PlayerShearEntityEvent; ++import org.bukkit.inventory.InventoryView; + + public class EntitySheep extends EntityAnimal implements net.minecraftforge.common.IShearable + { +@@ -55,6 +59,11 @@ + { + return false; + } ++ ++ @Override ++ public InventoryView getBukkitView() { ++ return null; // TODO: O.O ++ } + }, 2, 1); + private static final Map DYE_TO_RGB = Maps.newEnumMap(EnumDyeColor.class); + private int sheepTimer; +@@ -79,6 +88,7 @@ + this.setSize(0.9F, 1.3F); + this.inventoryCrafting.setInventorySlotContents(0, new ItemStack(Items.DYE)); + this.inventoryCrafting.setInventorySlotContents(1, new ItemStack(Items.DYE)); ++ this.inventoryCrafting.resultInventory = new InventoryCraftResult(); // CraftBukkit - add result slot for event + } + + protected void initEntityAI() +@@ -193,6 +203,14 @@ + { + if (!this.world.isRemote) + { ++ // CraftBukkit start ++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((org.bukkit.entity.Player) player.getBukkitEntity(), this.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end ++ + this.setSheared(true); + int i = 1 + this.rand.nextInt(3); + +@@ -348,6 +366,13 @@ + + public void eatGrassBonus() + { ++ // CraftBukkit start ++ SheepRegrowWoolEvent event = new SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity()); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end ++ + this.setSheared(false); + + if (this.isChild()) diff --git a/patches/net/minecraft/entity/passive/EntitySquid.java.patch b/patches/net/minecraft/entity/passive/EntitySquid.java.patch new file mode 100644 index 00000000..92c34f90 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntitySquid.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntitySquid.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntitySquid.java +@@ -38,7 +38,7 @@ + { + super(worldIn); + this.setSize(0.8F, 0.8F); +- this.rand.setSeed((long)(1 + this.getEntityId())); ++ this.rand.setSeed((long)(1 + this.getEntityId())); // Paper + this.rotationVelocity = 1.0F / (this.rand.nextFloat() + 1.0F) * 0.2F; + } + +@@ -191,7 +191,7 @@ + + public boolean getCanSpawnHere() + { +- return this.posY > 45.0D && this.posY < (double)this.world.getSeaLevel() && super.getCanSpawnHere(); ++ return this.posY > this.world.spigotConfig.squidSpawnRangeMin && this.posY < (double)this.world.getSeaLevel() && super.getCanSpawnHere(); + } + + @SideOnly(Side.CLIENT) diff --git a/patches/net/minecraft/entity/passive/EntityTameable.java.patch b/patches/net/minecraft/entity/passive/EntityTameable.java.patch new file mode 100644 index 00000000..ea262bbf --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityTameable.java.patch @@ -0,0 +1,14 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityTameable.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityTameable.java +@@ -223,6 +223,11 @@ + return this.aiSit; + } + ++ public void setAISit(EntityAISit aiSit) ++ { ++ this.aiSit = aiSit; ++ } ++ + public boolean shouldAttackEntity(EntityLivingBase target, EntityLivingBase owner) + { + return true; diff --git a/patches/net/minecraft/entity/passive/EntityVillager.java.patch b/patches/net/minecraft/entity/passive/EntityVillager.java.patch new file mode 100644 index 00000000..753abc86 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityVillager.java.patch @@ -0,0 +1,156 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityVillager.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityVillager.java +@@ -92,6 +92,13 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.entity.CraftVillager; ++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; ++import org.bukkit.entity.Villager; ++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; ++import org.bukkit.event.entity.VillagerAcquireTradeEvent; ++import org.bukkit.event.entity.VillagerReplenishTradeEvent; + + public class EntityVillager extends EntityAgeable implements INpc, IMerchant + { +@@ -104,18 +111,18 @@ + @Nullable + private EntityPlayer buyingPlayer; + @Nullable +- private MerchantRecipeList buyingList; ++ public MerchantRecipeList buyingList; // PAIL private -> public + private int timeUntilReset; + private boolean needsInitilization; + private boolean isWillingToMate; +- private int wealth; ++ public int wealth; + private java.util.UUID lastBuyingPlayer; +- private int careerId; ++ public int careerId; // PAIL private -> public // PAIL rename careerID + private int careerLevel; + private boolean isLookingForHome; + private boolean areAdditionalTasksSet; +- private final InventoryBasic villagerInventory; +- private static final EntityVillager.ITradeList[][][][] DEFAULT_TRADE_LIST_MAP = new EntityVillager.ITradeList[][][][] {{{{new EntityVillager.EmeraldForItems(Items.WHEAT, new EntityVillager.PriceInfo(18, 22)), new EntityVillager.EmeraldForItems(Items.POTATO, new EntityVillager.PriceInfo(15, 19)), new EntityVillager.EmeraldForItems(Items.CARROT, new EntityVillager.PriceInfo(15, 19)), new EntityVillager.ListItemForEmeralds(Items.BREAD, new EntityVillager.PriceInfo(-4, -2))}, {new EntityVillager.EmeraldForItems(Item.getItemFromBlock(Blocks.PUMPKIN), new EntityVillager.PriceInfo(8, 13)), new EntityVillager.ListItemForEmeralds(Items.PUMPKIN_PIE, new EntityVillager.PriceInfo(-3, -2))}, {new EntityVillager.EmeraldForItems(Item.getItemFromBlock(Blocks.MELON_BLOCK), new EntityVillager.PriceInfo(7, 12)), new EntityVillager.ListItemForEmeralds(Items.APPLE, new EntityVillager.PriceInfo(-7, -5))}, {new EntityVillager.ListItemForEmeralds(Items.COOKIE, new EntityVillager.PriceInfo(-10, -6)), new EntityVillager.ListItemForEmeralds(Items.CAKE, new EntityVillager.PriceInfo(1, 1))}}, {{new EntityVillager.EmeraldForItems(Items.STRING, new EntityVillager.PriceInfo(15, 20)), new EntityVillager.EmeraldForItems(Items.COAL, new EntityVillager.PriceInfo(16, 24)), new EntityVillager.ItemAndEmeraldToItem(Items.FISH, new EntityVillager.PriceInfo(6, 6), Items.COOKED_FISH, new EntityVillager.PriceInfo(6, 6))}, {new EntityVillager.ListEnchantedItemForEmeralds(Items.FISHING_ROD, new EntityVillager.PriceInfo(7, 8))}}, {{new EntityVillager.EmeraldForItems(Item.getItemFromBlock(Blocks.WOOL), new EntityVillager.PriceInfo(16, 22)), new EntityVillager.ListItemForEmeralds(Items.SHEARS, new EntityVillager.PriceInfo(3, 4))}, {new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL)), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 1), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 2), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 3), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 4), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 5), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 6), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 7), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 8), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 9), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 10), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 11), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 12), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 13), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 14), new EntityVillager.PriceInfo(1, 2)), new EntityVillager.ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 15), new EntityVillager.PriceInfo(1, 2))}}, {{new EntityVillager.EmeraldForItems(Items.STRING, new EntityVillager.PriceInfo(15, 20)), new EntityVillager.ListItemForEmeralds(Items.ARROW, new EntityVillager.PriceInfo(-12, -8))}, {new EntityVillager.ListItemForEmeralds(Items.BOW, new EntityVillager.PriceInfo(2, 3)), new EntityVillager.ItemAndEmeraldToItem(Item.getItemFromBlock(Blocks.GRAVEL), new EntityVillager.PriceInfo(10, 10), Items.FLINT, new EntityVillager.PriceInfo(6, 10))}}}, {{{new EntityVillager.EmeraldForItems(Items.PAPER, new EntityVillager.PriceInfo(24, 36)), new EntityVillager.ListEnchantedBookForEmeralds()}, {new EntityVillager.EmeraldForItems(Items.BOOK, new EntityVillager.PriceInfo(8, 10)), new EntityVillager.ListItemForEmeralds(Items.COMPASS, new EntityVillager.PriceInfo(10, 12)), new EntityVillager.ListItemForEmeralds(Item.getItemFromBlock(Blocks.BOOKSHELF), new EntityVillager.PriceInfo(3, 4))}, {new EntityVillager.EmeraldForItems(Items.WRITTEN_BOOK, new EntityVillager.PriceInfo(2, 2)), new EntityVillager.ListItemForEmeralds(Items.CLOCK, new EntityVillager.PriceInfo(10, 12)), new EntityVillager.ListItemForEmeralds(Item.getItemFromBlock(Blocks.GLASS), new EntityVillager.PriceInfo(-5, -3))}, {new EntityVillager.ListEnchantedBookForEmeralds()}, {new EntityVillager.ListEnchantedBookForEmeralds()}, {new EntityVillager.ListItemForEmeralds(Items.NAME_TAG, new EntityVillager.PriceInfo(20, 22))}}, {{new EntityVillager.EmeraldForItems(Items.PAPER, new EntityVillager.PriceInfo(24, 36))}, {new EntityVillager.EmeraldForItems(Items.COMPASS, new EntityVillager.PriceInfo(1, 1))}, {new EntityVillager.ListItemForEmeralds(Items.MAP, new EntityVillager.PriceInfo(7, 11))}, {new EntityVillager.TreasureMapForEmeralds(new EntityVillager.PriceInfo(12, 20), "Monument", MapDecoration.Type.MONUMENT), new EntityVillager.TreasureMapForEmeralds(new EntityVillager.PriceInfo(16, 28), "Mansion", MapDecoration.Type.MANSION)}}}, {{{new EntityVillager.EmeraldForItems(Items.ROTTEN_FLESH, new EntityVillager.PriceInfo(36, 40)), new EntityVillager.EmeraldForItems(Items.GOLD_INGOT, new EntityVillager.PriceInfo(8, 10))}, {new EntityVillager.ListItemForEmeralds(Items.REDSTONE, new EntityVillager.PriceInfo(-4, -1)), new EntityVillager.ListItemForEmeralds(new ItemStack(Items.DYE, 1, EnumDyeColor.BLUE.getDyeDamage()), new EntityVillager.PriceInfo(-2, -1))}, {new EntityVillager.ListItemForEmeralds(Items.ENDER_PEARL, new EntityVillager.PriceInfo(4, 7)), new EntityVillager.ListItemForEmeralds(Item.getItemFromBlock(Blocks.GLOWSTONE), new EntityVillager.PriceInfo(-3, -1))}, {new EntityVillager.ListItemForEmeralds(Items.EXPERIENCE_BOTTLE, new EntityVillager.PriceInfo(3, 11))}}}, {{{new EntityVillager.EmeraldForItems(Items.COAL, new EntityVillager.PriceInfo(16, 24)), new EntityVillager.ListItemForEmeralds(Items.IRON_HELMET, new EntityVillager.PriceInfo(4, 6))}, {new EntityVillager.EmeraldForItems(Items.IRON_INGOT, new EntityVillager.PriceInfo(7, 9)), new EntityVillager.ListItemForEmeralds(Items.IRON_CHESTPLATE, new EntityVillager.PriceInfo(10, 14))}, {new EntityVillager.EmeraldForItems(Items.DIAMOND, new EntityVillager.PriceInfo(3, 4)), new EntityVillager.ListEnchantedItemForEmeralds(Items.DIAMOND_CHESTPLATE, new EntityVillager.PriceInfo(16, 19))}, {new EntityVillager.ListItemForEmeralds(Items.CHAINMAIL_BOOTS, new EntityVillager.PriceInfo(5, 7)), new EntityVillager.ListItemForEmeralds(Items.CHAINMAIL_LEGGINGS, new EntityVillager.PriceInfo(9, 11)), new EntityVillager.ListItemForEmeralds(Items.CHAINMAIL_HELMET, new EntityVillager.PriceInfo(5, 7)), new EntityVillager.ListItemForEmeralds(Items.CHAINMAIL_CHESTPLATE, new EntityVillager.PriceInfo(11, 15))}}, {{new EntityVillager.EmeraldForItems(Items.COAL, new EntityVillager.PriceInfo(16, 24)), new EntityVillager.ListItemForEmeralds(Items.IRON_AXE, new EntityVillager.PriceInfo(6, 8))}, {new EntityVillager.EmeraldForItems(Items.IRON_INGOT, new EntityVillager.PriceInfo(7, 9)), new EntityVillager.ListEnchantedItemForEmeralds(Items.IRON_SWORD, new EntityVillager.PriceInfo(9, 10))}, {new EntityVillager.EmeraldForItems(Items.DIAMOND, new EntityVillager.PriceInfo(3, 4)), new EntityVillager.ListEnchantedItemForEmeralds(Items.DIAMOND_SWORD, new EntityVillager.PriceInfo(12, 15)), new EntityVillager.ListEnchantedItemForEmeralds(Items.DIAMOND_AXE, new EntityVillager.PriceInfo(9, 12))}}, {{new EntityVillager.EmeraldForItems(Items.COAL, new EntityVillager.PriceInfo(16, 24)), new EntityVillager.ListEnchantedItemForEmeralds(Items.IRON_SHOVEL, new EntityVillager.PriceInfo(5, 7))}, {new EntityVillager.EmeraldForItems(Items.IRON_INGOT, new EntityVillager.PriceInfo(7, 9)), new EntityVillager.ListEnchantedItemForEmeralds(Items.IRON_PICKAXE, new EntityVillager.PriceInfo(9, 11))}, {new EntityVillager.EmeraldForItems(Items.DIAMOND, new EntityVillager.PriceInfo(3, 4)), new EntityVillager.ListEnchantedItemForEmeralds(Items.DIAMOND_PICKAXE, new EntityVillager.PriceInfo(12, 15))}}}, {{{new EntityVillager.EmeraldForItems(Items.PORKCHOP, new EntityVillager.PriceInfo(14, 18)), new EntityVillager.EmeraldForItems(Items.CHICKEN, new EntityVillager.PriceInfo(14, 18))}, {new EntityVillager.EmeraldForItems(Items.COAL, new EntityVillager.PriceInfo(16, 24)), new EntityVillager.ListItemForEmeralds(Items.COOKED_PORKCHOP, new EntityVillager.PriceInfo(-7, -5)), new EntityVillager.ListItemForEmeralds(Items.COOKED_CHICKEN, new EntityVillager.PriceInfo(-8, -6))}}, {{new EntityVillager.EmeraldForItems(Items.LEATHER, new EntityVillager.PriceInfo(9, 12)), new EntityVillager.ListItemForEmeralds(Items.LEATHER_LEGGINGS, new EntityVillager.PriceInfo(2, 4))}, {new EntityVillager.ListEnchantedItemForEmeralds(Items.LEATHER_CHESTPLATE, new EntityVillager.PriceInfo(7, 12))}, {new EntityVillager.ListItemForEmeralds(Items.SADDLE, new EntityVillager.PriceInfo(8, 10))}}}, {new EntityVillager.ITradeList[0][]}}; ++ public final InventoryBasic villagerInventory; ++ private static final ITradeList[][][][] DEFAULT_TRADE_LIST_MAP = new ITradeList[][][][] {{{{new EmeraldForItems(Items.WHEAT, new PriceInfo(18, 22)), new EmeraldForItems(Items.POTATO, new PriceInfo(15, 19)), new EmeraldForItems(Items.CARROT, new PriceInfo(15, 19)), new ListItemForEmeralds(Items.BREAD, new PriceInfo(-4, -2))}, {new EmeraldForItems(Item.getItemFromBlock(Blocks.PUMPKIN), new PriceInfo(8, 13)), new ListItemForEmeralds(Items.PUMPKIN_PIE, new PriceInfo(-3, -2))}, {new EmeraldForItems(Item.getItemFromBlock(Blocks.MELON_BLOCK), new PriceInfo(7, 12)), new ListItemForEmeralds(Items.APPLE, new PriceInfo(-7, -5))}, {new ListItemForEmeralds(Items.COOKIE, new PriceInfo(-10, -6)), new ListItemForEmeralds(Items.CAKE, new PriceInfo(1, 1))}}, {{new EmeraldForItems(Items.STRING, new PriceInfo(15, 20)), new EmeraldForItems(Items.COAL, new PriceInfo(16, 24)), new ItemAndEmeraldToItem(Items.FISH, new PriceInfo(6, 6), Items.COOKED_FISH, new PriceInfo(6, 6))}, {new ListEnchantedItemForEmeralds(Items.FISHING_ROD, new PriceInfo(7, 8))}}, {{new EmeraldForItems(Item.getItemFromBlock(Blocks.WOOL), new PriceInfo(16, 22)), new ListItemForEmeralds(Items.SHEARS, new PriceInfo(3, 4))}, {new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL)), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 1), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 2), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 3), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 4), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 5), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 6), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 7), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 8), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 9), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 10), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 11), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 12), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 13), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 14), new PriceInfo(1, 2)), new ListItemForEmeralds(new ItemStack(Item.getItemFromBlock(Blocks.WOOL), 1, 15), new PriceInfo(1, 2))}}, {{new EmeraldForItems(Items.STRING, new PriceInfo(15, 20)), new ListItemForEmeralds(Items.ARROW, new PriceInfo(-12, -8))}, {new ListItemForEmeralds(Items.BOW, new PriceInfo(2, 3)), new ItemAndEmeraldToItem(Item.getItemFromBlock(Blocks.GRAVEL), new PriceInfo(10, 10), Items.FLINT, new PriceInfo(6, 10))}}}, {{{new EmeraldForItems(Items.PAPER, new PriceInfo(24, 36)), new ListEnchantedBookForEmeralds()}, {new EmeraldForItems(Items.BOOK, new PriceInfo(8, 10)), new ListItemForEmeralds(Items.COMPASS, new PriceInfo(10, 12)), new ListItemForEmeralds(Item.getItemFromBlock(Blocks.BOOKSHELF), new PriceInfo(3, 4))}, {new EmeraldForItems(Items.WRITTEN_BOOK, new PriceInfo(2, 2)), new ListItemForEmeralds(Items.CLOCK, new PriceInfo(10, 12)), new ListItemForEmeralds(Item.getItemFromBlock(Blocks.GLASS), new PriceInfo(-5, -3))}, {new ListEnchantedBookForEmeralds()}, {new ListEnchantedBookForEmeralds()}, {new ListItemForEmeralds(Items.NAME_TAG, new PriceInfo(20, 22))}}, {{new EmeraldForItems(Items.PAPER, new PriceInfo(24, 36))}, {new EmeraldForItems(Items.COMPASS, new PriceInfo(1, 1))}, {new ListItemForEmeralds(Items.MAP, new PriceInfo(7, 11))}, {new TreasureMapForEmeralds(new PriceInfo(12, 20), "Monument", MapDecoration.Type.MONUMENT), new TreasureMapForEmeralds(new PriceInfo(16, 28), "Mansion", MapDecoration.Type.MANSION)}}}, {{{new EmeraldForItems(Items.ROTTEN_FLESH, new PriceInfo(36, 40)), new EmeraldForItems(Items.GOLD_INGOT, new PriceInfo(8, 10))}, {new ListItemForEmeralds(Items.REDSTONE, new PriceInfo(-4, -1)), new ListItemForEmeralds(new ItemStack(Items.DYE, 1, EnumDyeColor.BLUE.getDyeDamage()), new PriceInfo(-2, -1))}, {new ListItemForEmeralds(Items.ENDER_PEARL, new PriceInfo(4, 7)), new ListItemForEmeralds(Item.getItemFromBlock(Blocks.GLOWSTONE), new PriceInfo(-3, -1))}, {new ListItemForEmeralds(Items.EXPERIENCE_BOTTLE, new PriceInfo(3, 11))}}}, {{{new EmeraldForItems(Items.COAL, new PriceInfo(16, 24)), new ListItemForEmeralds(Items.IRON_HELMET, new PriceInfo(4, 6))}, {new EmeraldForItems(Items.IRON_INGOT, new PriceInfo(7, 9)), new ListItemForEmeralds(Items.IRON_CHESTPLATE, new PriceInfo(10, 14))}, {new EmeraldForItems(Items.DIAMOND, new PriceInfo(3, 4)), new ListEnchantedItemForEmeralds(Items.DIAMOND_CHESTPLATE, new PriceInfo(16, 19))}, {new ListItemForEmeralds(Items.CHAINMAIL_BOOTS, new PriceInfo(5, 7)), new ListItemForEmeralds(Items.CHAINMAIL_LEGGINGS, new PriceInfo(9, 11)), new ListItemForEmeralds(Items.CHAINMAIL_HELMET, new PriceInfo(5, 7)), new ListItemForEmeralds(Items.CHAINMAIL_CHESTPLATE, new PriceInfo(11, 15))}}, {{new EmeraldForItems(Items.COAL, new PriceInfo(16, 24)), new ListItemForEmeralds(Items.IRON_AXE, new PriceInfo(6, 8))}, {new EmeraldForItems(Items.IRON_INGOT, new PriceInfo(7, 9)), new ListEnchantedItemForEmeralds(Items.IRON_SWORD, new PriceInfo(9, 10))}, {new EmeraldForItems(Items.DIAMOND, new PriceInfo(3, 4)), new ListEnchantedItemForEmeralds(Items.DIAMOND_SWORD, new PriceInfo(12, 15)), new ListEnchantedItemForEmeralds(Items.DIAMOND_AXE, new PriceInfo(9, 12))}}, {{new EmeraldForItems(Items.COAL, new PriceInfo(16, 24)), new ListEnchantedItemForEmeralds(Items.IRON_SHOVEL, new PriceInfo(5, 7))}, {new EmeraldForItems(Items.IRON_INGOT, new PriceInfo(7, 9)), new ListEnchantedItemForEmeralds(Items.IRON_PICKAXE, new PriceInfo(9, 11))}, {new EmeraldForItems(Items.DIAMOND, new PriceInfo(3, 4)), new ListEnchantedItemForEmeralds(Items.DIAMOND_PICKAXE, new PriceInfo(12, 15))}}}, {{{new EmeraldForItems(Items.PORKCHOP, new PriceInfo(14, 18)), new EmeraldForItems(Items.CHICKEN, new PriceInfo(14, 18))}, {new EmeraldForItems(Items.COAL, new PriceInfo(16, 24)), new ListItemForEmeralds(Items.COOKED_PORKCHOP, new PriceInfo(-7, -5)), new ListItemForEmeralds(Items.COOKED_CHICKEN, new PriceInfo(-8, -6))}}, {{new EmeraldForItems(Items.LEATHER, new PriceInfo(9, 12)), new ListItemForEmeralds(Items.LEATHER_LEGGINGS, new PriceInfo(2, 4))}, {new ListEnchantedItemForEmeralds(Items.LEATHER_CHESTPLATE, new PriceInfo(7, 12))}, {new ListItemForEmeralds(Items.SADDLE, new PriceInfo(8, 10))}}}, {new ITradeList[0][]}}; + + public EntityVillager(World worldIn) + { +@@ -125,11 +132,11 @@ + public EntityVillager(World worldIn, int professionId) + { + super(worldIn); +- this.villagerInventory = new InventoryBasic("Items", false, 8); ++ this.villagerInventory = new InventoryBasic("Items", false, 8, (CraftVillager) this.getBukkitEntity()); // CraftBukkit add argument + this.setProfession(professionId); + this.setSize(0.6F, 1.95F); + ((PathNavigateGround)this.getNavigator()).setBreakDoors(true); +- this.setCanPickUpLoot(true); ++ this.idkwhyreyoudoingthis(true); + } + + protected void initEntityAI() +@@ -186,6 +193,22 @@ + this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5D); + } + ++ // Spigot Start ++ @Override ++ public void inactiveTick() { ++ // SPIGOT-3874 ++ if (world.spigotConfig.tickInactiveVillagers) { ++ // SPIGOT-3894 ++ net.minecraft.world.chunk.Chunk startingChunk = this.world.getChunkIfLoaded(MathHelper.floor(this.posX) >> 4, MathHelper.floor(this.posZ) >> 4); ++ if (!(startingChunk != null )) { ++ return; ++ } ++ this.updateAITasks(); // SPIGOT-3846 ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ + protected void updateAITasks() + { + if (--this.randomTickDivider <= 0) +@@ -224,7 +247,14 @@ + { + if (merchantrecipe.isRecipeDisabled()) + { +- merchantrecipe.increaseMaxTradeUses(this.rand.nextInt(6) + this.rand.nextInt(6) + 2); ++ // CraftBukkit start ++ int bonus = this.rand.nextInt(6) + this.rand.nextInt(6) + 2; ++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((Villager) this.getBukkitEntity(), merchantrecipe.asBukkit(), bonus); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ merchantrecipe.increaseMaxTradeUses(event.getBonus()); ++ } ++ // CraftBukkit end + } + } + +@@ -388,11 +418,11 @@ + } + } + +- this.setCanPickUpLoot(true); ++ this.idkwhyreyoudoingthis(true); + this.setAdditionalAItasks(); + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return false; + } +@@ -654,7 +684,7 @@ + return net.minecraftforge.event.ForgeEventFactory.listTradeOffers(this, player, buyingList); + } + +- private void populateBuyingList() ++ public void populateBuyingList() // CraftBukkit private -> public // PAIL rename populateBuyingList + { + if (this.careerId != 0 && this.careerLevel != 0) + { +@@ -679,7 +709,20 @@ + { + for (EntityVillager.ITradeList entityvillager$itradelist : trades) + { +- entityvillager$itradelist.addMerchantRecipe(this, this.buyingList, this.rand); ++ // CraftBukkit start ++ // this is a hack. this must be done because otherwise, if ++ // mojang adds a new type of villager merchant option, it will need to ++ // have event handling added manually. this is better than having to do that. ++ MerchantRecipeList list = new MerchantRecipeList(); ++ entityvillager$itradelist.addMerchantRecipe(this, list /*this.buyingList*/, this.rand); ++ for (MerchantRecipe recipe : list) { ++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((Villager) getBukkitEntity(), recipe.asBukkit()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.buyingList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ } ++ } ++ // CraftBukkit end + } + } + } +@@ -880,7 +923,7 @@ + return false; + } + +- public void onStruckByLightning(EntityLightningBolt lightningBolt) ++ public void onStruckByLightning(@Nullable EntityLightningBolt lightningBolt) + { + if (!this.world.isRemote && !this.isDead) + { +@@ -895,7 +938,7 @@ + entitywitch.setAlwaysRenderNameTag(this.getAlwaysRenderNameTag()); + } + +- this.world.spawnEntity(entitywitch); ++ this.world.spawnEntity(entitywitch, SpawnReason.LIGHTNING); + this.setDead(); + } + } diff --git a/patches/net/minecraft/entity/passive/EntityWaterMob.java.patch b/patches/net/minecraft/entity/passive/EntityWaterMob.java.patch new file mode 100644 index 00000000..7aa25254 --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityWaterMob.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityWaterMob.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityWaterMob.java +@@ -32,7 +32,7 @@ + return 120; + } + +- protected boolean canDespawn() ++ public boolean canDespawn() + { + return true; + } diff --git a/patches/net/minecraft/entity/passive/EntityWolf.java.patch b/patches/net/minecraft/entity/passive/EntityWolf.java.patch new file mode 100644 index 00000000..8a7f322a --- /dev/null +++ b/patches/net/minecraft/entity/passive/EntityWolf.java.patch @@ -0,0 +1,90 @@ +--- ../src-base/minecraft/net/minecraft/entity/passive/EntityWolf.java ++++ ../src-work/minecraft/net/minecraft/entity/passive/EntityWolf.java +@@ -51,6 +51,8 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; + + public class EntityWolf extends EntityTameable + { +@@ -76,7 +78,7 @@ + this.aiSit = new EntityAISit(this); + this.tasks.addTask(1, new EntityAISwimming(this)); + this.tasks.addTask(2, this.aiSit); +- this.tasks.addTask(3, new EntityWolf.AIAvoidEntity(this, EntityLlama.class, 24.0F, 1.5D, 1.5D)); ++ this.tasks.addTask(3, new AIAvoidEntity(this, EntityLlama.class, 24.0F, 1.5D, 1.5D)); + this.tasks.addTask(4, new EntityAILeapAtTarget(this, 0.4F)); + this.tasks.addTask(5, new EntityAIAttackMelee(this, 1.0D, true)); + this.tasks.addTask(6, new EntityAIFollowOwner(this, 1.0D, 10.0F, 2.0F)); +@@ -115,6 +117,22 @@ + this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(2.0D); + } + ++ // CraftBukkit - add overriden version ++ @Override ++ public boolean setAttackTarget(@Nullable EntityLivingBase entityliving, EntityTargetEvent.TargetReason reason, boolean fire) { ++ if (!super.setAttackTarget(entityliving, reason, fire)) { ++ return false; ++ } ++ entityliving = getAttackTarget(); ++ if (entityliving == null) { ++ this.setAngry(false); ++ } else if (!this.isTamed()) { ++ this.setAngry(true); ++ } ++ return true; ++ } ++ // CraftBukkit end ++ + public void setAttackTarget(@Nullable EntityLivingBase entitylivingbaseIn) + { + super.setAttackTarget(entitylivingbaseIn); +@@ -336,7 +354,8 @@ + + if (this.aiSit != null) + { +- this.aiSit.setSitting(false); ++ // CraftBukkit - moved into EntityLiving.damageEntity_CB(DamageSource, float) ++ // this.aiSit.setSitting(false); + } + + if (entity != null && !(entity instanceof EntityPlayer) && !(entity instanceof EntityArrow)) +@@ -395,7 +414,7 @@ + itemstack.shrink(1); + } + +- this.heal((float)itemfood.getHealAmount(itemstack)); ++ this.heal((float)itemfood.getHealAmount(itemstack), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); + return true; + } + } +@@ -422,7 +441,7 @@ + this.aiSit.setSitting(!this.isSitting()); + this.isJumping = false; + this.navigator.clearPath(); +- this.setAttackTarget((EntityLivingBase)null); ++ this.setAttackTarget((EntityLivingBase)null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit + } + } + else if (itemstack.getItem() == Items.BONE && !this.isAngry()) +@@ -434,13 +453,16 @@ + + if (!this.world.isRemote) + { +- if (this.rand.nextInt(3) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player)) ++ // CraftBukkit - added event call and isCancelled check. ++ if (this.rand.nextInt(3) == 0 && !net.minecraftforge.event.ForgeEventFactory.onAnimalTame(this, player) && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) + { + this.setTamedBy(player); + this.navigator.clearPath(); + this.setAttackTarget((EntityLivingBase)null); + this.aiSit.setSitting(true); +- this.setHealth(20.0F); ++ // CraftBukkit - 20.0 -> getMaxHealth() ++ // this.setHealth(20.0F); ++ this.setHealth(this.getMaxHealth()); + this.playTameEffect(true); + this.world.setEntityState(this, (byte)7); + } diff --git a/patches/net/minecraft/entity/player/EntityPlayer.java.patch b/patches/net/minecraft/entity/player/EntityPlayer.java.patch new file mode 100644 index 00000000..375c13bf --- /dev/null +++ b/patches/net/minecraft/entity/player/EntityPlayer.java.patch @@ -0,0 +1,651 @@ +--- ../src-base/minecraft/net/minecraft/entity/player/EntityPlayer.java ++++ ../src-work/minecraft/net/minecraft/entity/player/EntityPlayer.java +@@ -92,6 +92,16 @@ + import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.entity.CraftItem; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.player.PlayerBedEnterEvent; ++import org.bukkit.event.player.PlayerBedLeaveEvent; ++import org.bukkit.event.player.PlayerDropItemEvent; ++import org.bukkit.event.player.PlayerVelocityEvent; ++import org.bukkit.util.Vector; + + @SuppressWarnings("incomplete-switch") + public abstract class EntityPlayer extends EntityLivingBase +@@ -109,10 +119,10 @@ + protected static final DataParameter LEFT_SHOULDER_ENTITY = EntityDataManager.createKey(EntityPlayer.class, DataSerializers.COMPOUND_TAG); + protected static final DataParameter RIGHT_SHOULDER_ENTITY = EntityDataManager.createKey(EntityPlayer.class, DataSerializers.COMPOUND_TAG); + public InventoryPlayer inventory = new InventoryPlayer(this); +- protected InventoryEnderChest enderChest = new InventoryEnderChest(); ++ protected InventoryEnderChest enderChest = new InventoryEnderChest(this); // CraftBukkit - add "this" to constructor + public Container inventoryContainer; + public Container openContainer; +- protected FoodStats foodStats = new FoodStats(); ++ protected FoodStats foodStats = new FoodStats(); // Kettle - Restore CraftBukkit modifications, compatible with AppleCore and other mods that modify FoodStats + protected int flyToggleTimer; + public float prevCameraYaw; + public float cameraYaw; +@@ -123,9 +133,9 @@ + public double chasingPosX; + public double chasingPosY; + public double chasingPosZ; +- protected boolean sleeping; ++ public boolean sleeping; + public BlockPos bedLocation; +- private int sleepTimer; ++ public int sleepTimer; + public float renderOffsetX; + @SideOnly(Side.CLIENT) + public float renderOffsetY; +@@ -147,6 +157,12 @@ + @Nullable + public EntityFishHook fishEntity; + ++ // CraftBukkit start ++ public boolean fauxSleeping; ++ public String spawnWorld = ""; ++ public int oldLevel = -1; ++ // CraftBukkit end ++ + protected CooldownTracker createCooldownTracker() + { + return new CooldownTracker(); +@@ -159,9 +175,13 @@ + this.gameProfile = gameProfileIn; + this.inventoryContainer = new ContainerPlayer(this.inventory, !worldIn.isRemote, this); + this.openContainer = this.inventoryContainer; ++ this.foodStats.player = this; + BlockPos blockpos = worldIn.getSpawnPoint(); + this.setLocationAndAngles((double)blockpos.getX() + 0.5D, (double)(blockpos.getY() + 1), (double)blockpos.getZ() + 0.5D, 0.0F, 0.0F); + this.unused180 = 180.0F; ++ ++ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityEvent.EntityConstructing(this)); ++ super.capabilities = net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(this); + } + + protected void applyEntityAttributes() +@@ -408,7 +428,7 @@ + return SoundCategory.PLAYERS; + } + +- protected int getFireImmuneTicks() ++ public int getFireImmuneTicks() + { + return 20; + } +@@ -499,7 +519,8 @@ + { + if (this.getHealth() < this.getMaxHealth() && this.ticksExisted % 20 == 0) + { +- this.heal(1.0F); ++ // CraftBukkit - added regain reason of "REGEN" for filtering purposes. ++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); + } + + if (this.foodStats.needFood() && this.ticksExisted % 10 == 0) +@@ -527,7 +548,7 @@ + + this.setAIMoveSpeed((float)iattributeinstance.getAttributeValue()); + float f = MathHelper.sqrt(this.motionX * this.motionX + this.motionZ * this.motionZ); +- float f1 = (float)(Math.atan(-this.motionY * 0.20000000298023224D) * 15.0D); ++ float f1 = (float) (org.bukkit.craftbukkit.TrigMath.atan(-this.motionY * 0.20000000298023224D) * 15.0D);// CraftBukkit + + if (f > 0.1F) + { +@@ -691,6 +712,7 @@ + @Nullable + public EntityItem dropItem(boolean dropAll) + { ++ // Called only when dropped by Q or CTRL-Q + ItemStack stack = inventory.getCurrentItem(); + + if (stack.isEmpty()) +@@ -750,6 +772,30 @@ + entityitem.motionZ += Math.sin((double)f3) * (double)f2; + } + ++ // CraftBukkit start - fire PlayerDropItemEvent ++ Player player = (Player) this.getBukkitEntity(); ++ CraftItem drop = new CraftItem(this.world.getServer(), entityitem); ++ ++ PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); ++ if (traceItem && (cur == null || cur.getAmount() == 0)) { ++ // The complete stack was dropped ++ player.getInventory().setItemInHand(drop.getItemStack()); ++ } else if (traceItem && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) { ++ // Only one item is dropped ++ cur.setAmount(cur.getAmount() + 1); ++ player.getInventory().setItemInHand(cur); ++ } else { ++ // Fallback ++ player.getInventory().addItem(drop.getItemStack()); ++ } ++ return null; ++ } ++ // CraftBukkit end ++ + ItemStack itemstack = this.dropItemAndGetStack(entityitem); + + if (traceItem) +@@ -893,6 +939,13 @@ + this.wakeUpPlayer(true, true, false); + } + ++ // CraftBukkit start ++ this.spawnWorld = compound.getString("SpawnWorld"); ++ if ("".equals(spawnWorld)) { ++ this.spawnWorld = this.world.getServer().getWorlds().get(0).getName(); ++ } ++ // CraftBukkit end ++ + if (compound.hasKey("SpawnX", 99) && compound.hasKey("SpawnY", 99) && compound.hasKey("SpawnZ", 99)) + { + this.spawnPos = new BlockPos(compound.getInteger("SpawnX"), compound.getInteger("SpawnY"), compound.getInteger("SpawnZ")); +@@ -987,6 +1040,7 @@ + { + compound.setTag("ShoulderEntityRight", this.getRightShoulderEntity()); + } ++ compound.setString("SpawnWorld", spawnWorld); // CraftBukkit - fixes bed spawns for multiworld worlds + } + + public boolean attackEntityFrom(DamageSource source, float amount) +@@ -1015,13 +1069,13 @@ + this.wakeUpPlayer(true, true, false); + } + +- this.spawnShoulderEntities(); ++ // this.spawnShoulderEntities(); // CraftBukkit - moved down + + if (source.isDifficultyScaled()) + { + if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL) + { +- amount = 0.0F; ++ return false; // CraftBukkit - f = 0.0f -> return false + } + + if (this.world.getDifficulty() == EnumDifficulty.EASY) +@@ -1035,7 +1089,14 @@ + } + } + +- return amount == 0.0F ? false : super.attackEntityFrom(source, amount); ++ // return amount == 0.0F ? false : super.attackEntityFrom(source, amount); ++ // CraftBukkit start - Don't filter out 0 damage ++ boolean damaged = super.attackEntityFrom(source, amount); ++ if (damaged) { ++ this.spawnShoulderEntities(); ++ } ++ return damaged; ++ // CraftBukkit end + } + } + } +@@ -1052,17 +1113,29 @@ + + public boolean canAttackPlayer(EntityPlayer other) + { +- Team team = this.getTeam(); +- Team team1 = other.getTeam(); +- +- if (team == null) +- { +- return true; ++ // Team team = this.getTeam(); ++ // Team team1 = other.getTeam(); ++ // CraftBukkit start - Change to check OTHER player's scoreboard team according to API ++ // To summarize this method's logic, it's "Can parameter hurt this" ++ org.bukkit.scoreboard.Team team; ++ if (other instanceof EntityPlayerMP) { ++ EntityPlayerMP thatPlayer = (EntityPlayerMP) other; ++ team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity()); ++ if (team == null || team.allowFriendlyFire()) { ++ return true; ++ } ++ } else { ++ // This should never be called, but is implemented anyway ++ org.bukkit.OfflinePlayer thisPlayer = other.world.getServer().getOfflinePlayer(other.getUniqueID()); ++ team = other.world.getServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer); ++ if (team == null || team.allowFriendlyFire()) { ++ return true; ++ } + } +- else +- { +- return !team.isSameTeam(team1) ? true : team.getAllowFriendlyFire(); ++ if (this instanceof EntityPlayerMP) { ++ return !team.hasEntry(((EntityPlayerMP) this).getBukkitEntity().getName()); + } ++ return !team.hasEntry(this.world.getServer().getOfflinePlayer(this.getUniqueID()).getName()); + } + + protected void damageArmor(float damage) +@@ -1140,6 +1213,7 @@ + } + } + } ++ return; + } + + public void openEditSign(TileEntitySign signTile) +@@ -1317,8 +1391,17 @@ + + if (j > 0 && !targetEntity.isBurning()) + { +- flag4 = true; +- targetEntity.setFire(1); ++ // flag4 = true; ++ // targetEntity.setFire(1); ++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item ++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), targetEntity.getBukkitEntity(), 1); ++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent); ++ ++ if (!combustEvent.isCancelled()) { ++ flag4 = true; ++ targetEntity.setFire(combustEvent.getDuration()); ++ } ++ // CraftBukkit end + } + } + +@@ -1353,8 +1436,10 @@ + { + if (entitylivingbase != this && entitylivingbase != targetEntity && !this.isOnSameTeam(entitylivingbase) && this.getDistanceSq(entitylivingbase) < 9.0D) + { +- entitylivingbase.knockBack(this, 0.4F, (double)MathHelper.sin(this.rotationYaw * 0.017453292F), (double)(-MathHelper.cos(this.rotationYaw * 0.017453292F))); +- entitylivingbase.attackEntityFrom(DamageSource.causePlayerDamage(this), f3); ++ // CraftBukkit start - Only apply knockback if the damage hits ++ if (entitylivingbase.attackEntityFrom(DamageSource.causePlayerDamage(this).sweep(), f3)) { ++ entitylivingbase.knockBack(this, 0.4F, (double) MathHelper.sin(this.rotationYaw * 0.017453292F), (double) (-MathHelper.cos(this.rotationYaw * 0.017453292F))); ++ } + } + } + +@@ -1364,11 +1449,28 @@ + + if (targetEntity instanceof EntityPlayerMP && targetEntity.velocityChanged) + { +- ((EntityPlayerMP)targetEntity).connection.sendPacket(new SPacketEntityVelocity(targetEntity)); +- targetEntity.velocityChanged = false; +- targetEntity.motionX = d1; +- targetEntity.motionY = d2; +- targetEntity.motionZ = d3; ++ // CraftBukkit start - Add Velocity Event ++ boolean cancelled = false; ++ Player player = (Player) targetEntity.getBukkitEntity(); ++ Vector velocity = new Vector( d1, d2, d3 ); ++ ++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone()); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ cancelled = true; ++ } else if (!velocity.equals(event.getVelocity())) { ++ player.setVelocity(event.getVelocity()); ++ } ++ ++ if (!cancelled) { ++ ((EntityPlayerMP) targetEntity).connection.sendPacket(new SPacketEntityVelocity(targetEntity)); ++ targetEntity.velocityChanged = false; ++ targetEntity.motionX = d1; ++ targetEntity.motionY = d2; ++ targetEntity.motionZ = d3; ++ } ++ // CraftBukkit end + } + + if (flag2) +@@ -1434,7 +1536,15 @@ + + if (j > 0) + { +- targetEntity.setFire(j * 4); ++ // targetEntity.setFire(j * 4); ++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item ++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), j * 4); ++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent); ++ ++ if (!combustEvent.isCancelled()) { ++ entity.setFire(combustEvent.getDuration()); ++ } ++ // CraftBukkit end + } + + if (this.world instanceof WorldServer && f5 > 2.0F) +@@ -1443,8 +1553,7 @@ + ((WorldServer)this.world).spawnParticle(EnumParticleTypes.DAMAGE_INDICATOR, targetEntity.posX, targetEntity.posY + (double)(targetEntity.height * 0.5F), targetEntity.posZ, k, 0.1D, 0.0D, 0.1D, 0.2D); + } + } +- +- this.addExhaustion(0.1F); ++ this.addExhaustion(world.spigotConfig.combatExhaustion); // Spigot - Change to use configurable value + } + else + { +@@ -1454,6 +1563,11 @@ + { + targetEntity.extinguish(); + } ++ // CraftBukkit start - resync on cancelled event ++ if (this instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) this).getBukkitEntity().updateInventory(); ++ } ++ // CraftBukkit end + } + } + } +@@ -1527,9 +1641,9 @@ + return this.gameProfile; + } + +- public EntityPlayer.SleepResult trySleep(BlockPos bedLocation) ++ public SleepResult trySleep(BlockPos bedLocation) + { +- EntityPlayer.SleepResult ret = net.minecraftforge.event.ForgeEventFactory.onPlayerSleepInBed(this, bedLocation); ++ SleepResult ret = net.minecraftforge.event.ForgeEventFactory.onPlayerSleepInBed(this, bedLocation); + if (ret != null) return ret; + final IBlockState state = this.world.isBlockLoaded(bedLocation) ? this.world.getBlockState(bedLocation) : null; + final boolean isBed = state != null && state.getBlock().isBed(state, this.world, bedLocation, this); +@@ -1539,31 +1653,31 @@ + { + if (this.isPlayerSleeping() || !this.isEntityAlive()) + { +- return EntityPlayer.SleepResult.OTHER_PROBLEM; ++ return SleepResult.OTHER_PROBLEM; + } + + if (!this.world.provider.isSurfaceWorld()) + { +- return EntityPlayer.SleepResult.NOT_POSSIBLE_HERE; ++ return SleepResult.NOT_POSSIBLE_HERE; + } + +- if (!net.minecraftforge.event.ForgeEventFactory.fireSleepingTimeCheck(this, bedLocation)) ++ if (!net.minecraftforge.event.ForgeEventFactory.fireSleepingTimeCheck(this, this.bedLocation)) + { +- return EntityPlayer.SleepResult.NOT_POSSIBLE_NOW; ++ return SleepResult.NOT_POSSIBLE_NOW; + } + + if (!this.bedInRange(bedLocation, enumfacing)) + { +- return EntityPlayer.SleepResult.TOO_FAR_AWAY; ++ return SleepResult.TOO_FAR_AWAY; + } + + double d0 = 8.0D; + double d1 = 5.0D; +- List list = this.world.getEntitiesWithinAABB(EntityMob.class, new AxisAlignedBB((double)bedLocation.getX() - 8.0D, (double)bedLocation.getY() - 5.0D, (double)bedLocation.getZ() - 8.0D, (double)bedLocation.getX() + 8.0D, (double)bedLocation.getY() + 5.0D, (double)bedLocation.getZ() + 8.0D), new EntityPlayer.SleepEnemyPredicate(this)); ++ List list = this.world.getEntitiesWithinAABB(EntityMob.class, new AxisAlignedBB((double)bedLocation.getX() - 8.0D, (double)bedLocation.getY() - 5.0D, (double)bedLocation.getZ() - 8.0D, (double)bedLocation.getX() + 8.0D, (double)bedLocation.getY() + 5.0D, (double)bedLocation.getZ() + 8.0D), new SleepEnemyPredicate(this)); + + if (!list.isEmpty()) + { +- return EntityPlayer.SleepResult.NOT_SAFE; ++ return SleepResult.NOT_SAFE; + } + } + +@@ -1572,6 +1686,20 @@ + this.dismountRidingEntity(); + } + ++ // CraftBukkit start - fire PlayerBedEnterEvent ++ if (this.getBukkitEntity() instanceof Player) { ++ Player player = (Player) this.getBukkitEntity(); ++ org.bukkit.block.Block bed = this.world.getWorld().getBlockAt(bedLocation.getX(), bedLocation.getY(), bedLocation.getZ()); ++ ++ PlayerBedEnterEvent event = new PlayerBedEnterEvent(player, bed); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return SleepResult.OTHER_PROBLEM; ++ } ++ } ++ // CraftBukkit end ++ + this.spawnShoulderEntities(); + this.setSize(0.2F, 0.2F); + +@@ -1598,7 +1726,7 @@ + this.world.updateAllPlayersSleepingFlag(); + } + +- return EntityPlayer.SleepResult.OK; ++ return SleepResult.OK; + } + + private boolean bedInRange(BlockPos p_190774_1_, EnumFacing p_190774_2_) +@@ -1651,6 +1779,23 @@ + this.world.updateAllPlayersSleepingFlag(); + } + ++ // CraftBukkit start - fire PlayerBedLeaveEvent ++ if (this.getBukkitEntity() instanceof Player) { ++ Player player = (Player) this.getBukkitEntity(); ++ ++ org.bukkit.block.Block bed; ++ BlockPos blockposition = this.bedLocation; ++ if (blockposition != null) { ++ bed = this.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } else { ++ bed = this.world.getWorld().getBlockAt(player.getLocation()); ++ } ++ ++ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed); ++ this.world.getServer().getPluginManager().callEvent(event); ++ } ++ // CraftBukkit end ++ + this.sleepTimer = immediately ? 0 : 100; + + if (setSpawn) +@@ -1757,11 +1902,13 @@ + { + this.spawnPos = pos; + this.spawnForced = forced; ++ this.spawnWorld = this.world.worldInfo.getWorldName(); // CraftBukkit + } + else + { + this.spawnPos = null; + this.spawnForced = false; ++ this.spawnWorld = ""; // CraftBukkit + } + } + +@@ -1797,11 +1944,11 @@ + + if (this.isSprinting()) + { +- this.addExhaustion(0.2F); ++ this.addExhaustion(world.spigotConfig.jumpSprintExhaustion); // Spigot - Change to use configurable value + } + else + { +- this.addExhaustion(0.05F); ++ this.addExhaustion(world.spigotConfig.jumpWalkExhaustion); // Spigot - Change to use configurable value + } + } + +@@ -1820,7 +1967,12 @@ + this.motionY = d3 * 0.6D; + this.jumpMovementFactor = f; + this.fallDistance = 0.0F; +- this.setFlag(7, false); ++ // this.setFlag(7, false); ++ // CraftBukkit start ++ if (getFlag(7) && !org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) { ++ this.setFlag(7, false); ++ } ++ // CraftBukkit end + } + else + { +@@ -1846,7 +1998,7 @@ + if (i > 0) + { + this.addStat(StatList.DIVE_ONE_CM, i); +- this.addExhaustion(0.01F * (float)i * 0.01F); ++ this.addExhaustion(world.spigotConfig.swimMultiplier * (float) i * 0.01F); // Spigot + } + } + else if (this.isInWater()) +@@ -1856,7 +2008,7 @@ + if (j > 0) + { + this.addStat(StatList.SWIM_ONE_CM, j); +- this.addExhaustion(0.01F * (float)j * 0.01F); ++ this.addExhaustion(world.spigotConfig.swimMultiplier * (float) j * 0.01F); // Spigot + } + } + else if (this.isOnLadder()) +@@ -1875,17 +2027,17 @@ + if (this.isSprinting()) + { + this.addStat(StatList.SPRINT_ONE_CM, k); +- this.addExhaustion(0.1F * (float)k * 0.01F); ++ this.addExhaustion(world.spigotConfig.sprintMultiplier * (float) k * 0.01F); // Spigot + } + else if (this.isSneaking()) + { + this.addStat(StatList.CROUCH_ONE_CM, k); +- this.addExhaustion(0.0F * (float)k * 0.01F); ++ this.addExhaustion(world.spigotConfig.otherMultiplier * (float) k * 0.01F); // Spigot + } + else + { + this.addStat(StatList.WALK_ONE_CM, k); +- this.addExhaustion(0.0F * (float)k * 0.01F); ++ this.addExhaustion(world.spigotConfig.otherMultiplier * (float) k * 0.01F); // Spigot + } + } + } +@@ -2225,10 +2377,16 @@ + + protected void spawnShoulderEntities() + { +- this.spawnShoulderEntity(this.getLeftShoulderEntity()); +- this.setLeftShoulderEntity(new NBTTagCompound()); +- this.spawnShoulderEntity(this.getRightShoulderEntity()); +- this.setRightShoulderEntity(new NBTTagCompound()); ++ // this.spawnShoulderEntity(this.getLeftShoulderEntity()); ++ // this.setLeftShoulderEntity(new NBTTagCompound()); ++ // this.spawnShoulderEntity(this.getRightShoulderEntity()); ++ // this.setRightShoulderEntity(new NBTTagCompound()); ++ if (this.spawnShoulderEntity_CB(this.getLeftShoulderEntity())) { ++ this.setLeftShoulderEntity(new NBTTagCompound()); ++ } ++ if (this.spawnShoulderEntity_CB(this.getRightShoulderEntity())) { ++ this.setRightShoulderEntity(new NBTTagCompound()); ++ } + } + + private void spawnShoulderEntity(@Nullable NBTTagCompound p_192026_1_) +@@ -2247,6 +2405,23 @@ + } + } + ++ private boolean spawnShoulderEntity_CB(@Nullable NBTTagCompound p_192026_1_) // CraftBukkit void->boolean ++ { ++ if (!this.world.isRemote && !p_192026_1_.hasNoTags()) ++ { ++ Entity entity = EntityList.createEntityFromNBT(p_192026_1_, this.world); ++ ++ if (entity instanceof EntityTameable) ++ { ++ ((EntityTameable)entity).setOwnerId(this.entityUniqueID); ++ } ++ ++ entity.setPosition(this.posX, this.posY + 0.699999988079071D, this.posZ); ++ return this.world.spawnEntity(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); ++ } ++ return true; ++ } ++ + @SideOnly(Side.CLIENT) + public boolean isInvisibleToPlayer(EntityPlayer player) + { +@@ -2481,7 +2656,7 @@ + return (NBTTagCompound)this.dataManager.get(LEFT_SHOULDER_ENTITY); + } + +- protected void setLeftShoulderEntity(NBTTagCompound tag) ++ public void setLeftShoulderEntity(NBTTagCompound tag) + { + this.dataManager.set(LEFT_SHOULDER_ENTITY, tag); + } +@@ -2491,7 +2666,7 @@ + return (NBTTagCompound)this.dataManager.get(RIGHT_SHOULDER_ENTITY); + } + +- protected void setRightShoulderEntity(NBTTagCompound tag) ++ public void setRightShoulderEntity(NBTTagCompound tag) + { + this.dataManager.set(RIGHT_SHOULDER_ENTITY, tag); + } +@@ -2531,7 +2706,7 @@ + + public boolean canUseCommandBlock() + { +- return this.capabilities.isCreativeMode && this.canUseCommand(2, ""); ++ return this.capabilities.isCreativeMode && (this.canUseCommand(2, "") || this.canUseCommand(2, "", "minecraft")); + } + + /** +@@ -2671,7 +2846,7 @@ + @SuppressWarnings("unchecked") + @Override + @Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + if (capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + { +@@ -2683,7 +2858,7 @@ + } + + @Override +- public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public boolean hasCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + return capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY || super.hasCapability(capability, facing); + } +@@ -2702,7 +2877,7 @@ + SYSTEM(1, "options.chat.visibility.system"), + HIDDEN(2, "options.chat.visibility.hidden"); + +- private static final EntityPlayer.EnumChatVisibility[] ID_LOOKUP = new EntityPlayer.EnumChatVisibility[values().length]; ++ private static final EnumChatVisibility[] ID_LOOKUP = new EnumChatVisibility[values().length]; + private final int chatVisibility; + private final String resourceKey; + +@@ -2732,7 +2907,7 @@ + + static + { +- for (EntityPlayer.EnumChatVisibility entityplayer$enumchatvisibility : values()) ++ for (EnumChatVisibility entityplayer$enumchatvisibility : values()) + { + ID_LOOKUP[entityplayer$enumchatvisibility.chatVisibility] = entityplayer$enumchatvisibility; + } +@@ -2763,4 +2938,11 @@ + OTHER_PROBLEM, + NOT_SAFE; + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftHumanEntity getBukkitEntity() { ++ return (CraftHumanEntity) super.getBukkitEntity(); ++ } ++ // CraftBukkit end + } diff --git a/patches/net/minecraft/entity/player/EntityPlayerMP.java.patch b/patches/net/minecraft/entity/player/EntityPlayerMP.java.patch new file mode 100644 index 00000000..a992a2c5 --- /dev/null +++ b/patches/net/minecraft/entity/player/EntityPlayerMP.java.patch @@ -0,0 +1,982 @@ +--- ../src-base/minecraft/net/minecraft/entity/player/EntityPlayerMP.java ++++ ../src-work/minecraft/net/minecraft/entity/player/EntityPlayerMP.java +@@ -1,5 +1,6 @@ + package net.minecraft.entity.player; + ++import com.google.common.base.Preconditions; + import com.google.common.collect.Lists; + import com.mojang.authlib.GameProfile; + import io.netty.buffer.Unpooled; +@@ -13,10 +14,10 @@ + import net.minecraft.block.BlockFence; + import net.minecraft.block.BlockFenceGate; + import net.minecraft.block.BlockWall; +-import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.CrashReportCategory; ++import net.minecraft.enchantment.EnchantmentHelper; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityList; + import net.minecraft.entity.EntityLivingBase; +@@ -72,10 +73,10 @@ + import net.minecraft.scoreboard.Score; + import net.minecraft.scoreboard.ScoreObjective; + import net.minecraft.scoreboard.ScorePlayerTeam; ++import net.minecraft.scoreboard.Scoreboard; + import net.minecraft.scoreboard.Team; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.management.PlayerInteractionManager; +-import net.minecraft.server.management.UserListOpsEntry; + import net.minecraft.stats.RecipeBookServer; + import net.minecraft.stats.StatBase; + import net.minecraft.stats.StatList; +@@ -83,12 +84,14 @@ + import net.minecraft.tileentity.TileEntity; + import net.minecraft.tileentity.TileEntityCommandBlock; + import net.minecraft.tileentity.TileEntitySign; ++import net.minecraft.util.CombatTracker; + import net.minecraft.util.CooldownTracker; + import net.minecraft.util.CooldownTrackerServer; + import net.minecraft.util.DamageSource; + import net.minecraft.util.EntityDamageSource; + import net.minecraft.util.EnumHand; + import net.minecraft.util.EnumHandSide; ++import net.minecraft.util.FoodStats; + import net.minecraft.util.NonNullList; + import net.minecraft.util.ReportedException; + import net.minecraft.util.ResourceLocation; +@@ -109,21 +112,36 @@ + import net.minecraft.world.GameType; + import net.minecraft.world.IInteractionObject; + import net.minecraft.world.ILockableContainer; ++import net.minecraft.world.World; + import net.minecraft.world.WorldServer; + import net.minecraft.world.storage.loot.ILootContainer; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.GameMode; ++import org.bukkit.WeatherType; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.inventory.InventoryType; ++import org.bukkit.event.player.PlayerChangedMainHandEvent; ++import org.bukkit.event.player.PlayerGameModeChangeEvent; ++import org.bukkit.event.player.PlayerLocaleChangeEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.inventory.MainHand; + + public class EntityPlayerMP extends EntityPlayer implements IContainerListener + { + private static final Logger LOGGER = LogManager.getLogger(); +- private String language = "en_US"; ++ public String language = "en_us"; // CraftBukkit - lowercase ++ public long lastSave = MinecraftServer.currentTick; // Paper + public NetHandlerPlayServer connection; + public final MinecraftServer mcServer; + public final PlayerInteractionManager interactionManager; + public double managedPosX; + public double managedPosZ; +- private final List entityRemoveQueue = Lists.newLinkedList(); ++ public final List entityRemoveQueue = Lists.newLinkedList(); + private final PlayerAdvancements advancements; + private final StatisticsManagerServer statsFile; + private float lastHealthScore = Float.MIN_VALUE; +@@ -135,13 +153,13 @@ + private float lastHealth = -1.0E8F; + private int lastFoodLevel = -99999999; + private boolean wasHungry = true; +- private int lastExperience = -99999999; +- private int respawnInvulnerabilityTicks = 60; +- private EntityPlayer.EnumChatVisibility chatVisibility; ++ public int lastExperience = -99999999; ++ public int respawnInvulnerabilityTicks = 60; ++ private EnumChatVisibility chatVisibility; + private boolean chatColours = true; + private long playerLastActiveTime = System.currentTimeMillis(); + private Entity spectatingEntity; +- private boolean invulnerableDimensionChange; ++ public boolean invulnerableDimensionChange; + private boolean seenCredits; + private final RecipeBookServer recipeBook = new RecipeBookServer(); + private Vec3d levitationStartPos; +@@ -152,7 +170,26 @@ + public boolean isChangingQuantityOnly; + public int ping; + public boolean queuedEndExit; ++ private int viewDistance; ++ public String displayName; ++ public ITextComponent listName; ++ public org.bukkit.Location compassTarget; ++ public int newExp = 0; ++ public int newLevel = 0; ++ public int newTotalExp = 0; ++ public boolean keepLevel = false; ++ public double maxHealthCache; ++ public boolean joining = true; ++ public boolean sentListPacket = false; ++ // Paper end ++ public int getViewDistance() { ++ return (this.viewDistance == -1) ? ((WorldServer)this.world).getPlayerChunkMap().getViewDistance() : this.viewDistance; ++ } + ++ public void setViewDistance(final int viewDistance) { ++ this.viewDistance = viewDistance; ++ } ++ + public EntityPlayerMP(MinecraftServer server, WorldServer worldIn, GameProfile profile, PlayerInteractionManager interactionManagerIn) + { + super(worldIn, profile); +@@ -188,8 +225,33 @@ + { + this.setPosition(this.posX, this.posY + 1.0D, this.posZ); + } ++ ++ this.displayName = this.getName(); ++ this.thisisatest = true; ++ this.maxHealthCache = this.getMaxHealth(); + } + ++ public final BlockPos getSpawnPoint(MinecraftServer minecraftserver, WorldServer worldserver) { ++ BlockPos blockposition = worldserver.getSpawnPoint(); ++ ++ if (worldserver.provider.hasSkyLight() && worldserver.getWorldInfo().getGameType() != GameType.ADVENTURE) { ++ int i = Math.max(0, minecraftserver.getSpawnRadius(worldserver)); ++ int j = MathHelper.floor(worldserver.getWorldBorder().getClosestDistance((double) blockposition.getX(), (double) blockposition.getZ())); ++ ++ if (j < i) { ++ i = j; ++ } ++ ++ if (j <= 1) { ++ i = 1; ++ } ++ ++ blockposition = worldserver.getTopSolidOrLiquidBlock(blockposition.add(this.rand.nextInt(i * 2 + 1) - i, 0, this.rand.nextInt(i * 2 + 1) - i)); ++ } ++ ++ return blockposition; ++ } ++ + public void readEntityFromNBT(NBTTagCompound compound) + { + super.readEntityFromNBT(compound); +@@ -218,6 +280,7 @@ + { + this.recipeBook.read(compound.getCompoundTag("recipeBook")); + } ++ this.getBukkitEntity().readExtraData(compound); // CraftBukkit + } + + public static void registerFixesPlayerMP(DataFixer p_191522_0_) +@@ -270,8 +333,34 @@ + } + + compound.setTag("recipeBook", this.recipeBook.write()); ++ this.getBukkitEntity().setExtraData(compound); // CraftBukkit + } + ++ // CraftBukkit start - World fallback code, either respawn location or global spawn ++ public void setWorld(World world) { ++ super.setWorld(world); ++ if (world == null) { ++ this.dead = false; ++ BlockPos position = null; ++ if (this.spawnWorld != null && !this.spawnWorld.equals("")) { ++ CraftWorld cworld = (CraftWorld) Bukkit.getServer().getWorld(this.spawnWorld); ++ if (cworld != null && this.getBedLocation() != null) { ++ world = cworld.getHandle(); ++ position = EntityPlayer.getBedSpawnLocation(cworld.getHandle(), this.getBedLocation(), false); ++ } ++ } ++ if (world == null || position == null) { ++ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); ++ position = world.getSpawnPoint(); ++ } ++ this.world = world; ++ this.setPosition(position.getX() + 0.5, position.getY(), position.getZ() + 0.5); ++ } ++ this.dimension = ((WorldServer) this.world).dimension; ++ this.interactionManager.setWorld((WorldServer) world); ++ } ++ // CraftBukkit end ++ + public void addExperienceLevel(int levels) + { + super.addExperienceLevel(levels); +@@ -313,6 +402,11 @@ + + public void onUpdate() + { ++ // CraftBukkit start ++ if (this.joining) { ++ this.joining = false; ++ } ++ // CraftBukkit end + this.interactionManager.updateBlockRemoving(); + --this.respawnInvulnerabilityTicks; + +@@ -398,7 +492,7 @@ + + if (this.getHealth() != this.lastHealth || this.lastFoodLevel != this.foodStats.getFoodLevel() || this.foodStats.getSaturationLevel() == 0.0F != this.wasHungry) + { +- this.connection.sendPacket(new SPacketUpdateHealth(this.getHealth(), this.foodStats.getFoodLevel(), this.foodStats.getSaturationLevel())); ++ this.connection.sendPacket(new SPacketUpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodStats.getFoodLevel(), this.foodStats.getSaturationLevel())); // CraftBukkit + this.lastHealth = this.getHealth(); + this.lastFoodLevel = this.foodStats.getFoodLevel(); + this.wasHungry = this.foodStats.getSaturationLevel() == 0.0F; +@@ -422,6 +516,12 @@ + this.updateScorePoints(IScoreCriteria.AIR, MathHelper.ceil((float)this.lastAirScore)); + } + ++ // CraftBukkit start - Force max health updates ++ if (this.maxHealthCache != this.getMaxHealth()) { ++ this.getBukkitEntity().updateScaledHealth(); ++ } ++ // CraftBukkit end ++ + if (this.getTotalArmorValue() != this.lastArmorScore) + { + this.lastArmorScore = this.getTotalArmorValue(); +@@ -450,6 +550,16 @@ + { + CriteriaTriggers.LOCATION.trigger(this); + } ++ // CraftBukkit start - initialize oldLevel and fire PlayerLevelChangeEvent ++ if (this.oldLevel == -1) { ++ this.oldLevel = this.experienceLevel; ++ } ++ ++ if (this.oldLevel != this.experienceLevel) { ++ CraftEventFactory.callPlayerLevelChangeEvent(this.world.getServer().getPlayer(this), this.oldLevel, this.experienceLevel); ++ this.oldLevel = this.experienceLevel; ++ } ++ // CraftBukkit end + } + catch (Throwable throwable) + { +@@ -462,9 +572,9 @@ + + private void updateScorePoints(IScoreCriteria criteria, int points) + { +- for (ScoreObjective scoreobjective : this.getWorldScoreboard().getObjectivesFromCriteria(criteria)) ++ for (Score score : this.world.getServer().getScoreboardManager().getScoreboardScores(criteria, this.getName(), new java.util.ArrayList())) // CraftBukkit - Use our scores instead + { +- Score score = this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective); ++ // Score score = this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective); + score.setScorePoints(points); + } + } +@@ -474,29 +584,52 @@ + if (net.minecraftforge.common.ForgeHooks.onLivingDeath(this, cause)) return; + boolean flag = this.world.getGameRules().getBoolean("showDeathMessages"); + this.connection.sendPacket(new SPacketCombatEvent(this.getCombatTracker(), SPacketCombatEvent.Event.ENTITY_DIED, flag)); ++ if (this.dead) { ++ return; ++ } ++ List loot = new java.util.ArrayList<>(this.inventory.getSizeInventory()); ++ boolean keepInventory = this.world.getGameRules().getBoolean("keepInventory") || this.isSpectator(); + +- if (flag) +- { +- Team team = this.getTeam(); ++ if (!keepInventory) { ++ for (ItemStack item : this.inventory.getContents()) { ++ if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) { ++ loot.add(CraftItemStack.asCraftMirror(item)); ++ } ++ } ++ } + +- if (team != null && team.getDeathMessageVisibility() != Team.EnumVisible.ALWAYS) +- { +- if (team.getDeathMessageVisibility() == Team.EnumVisible.HIDE_FOR_OTHER_TEAMS) +- { +- this.mcServer.getPlayerList().sendMessageToAllTeamMembers(this, this.getCombatTracker().getDeathMessage()); ++ ITextComponent chatmessage = this.getCombatTracker().getDeathMessage(); ++ ++ String deathmessage = chatmessage.getFormattedText(); ++ org.bukkit.event.entity.PlayerDeathEvent deathEvent = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory); ++ String deathMessage = deathEvent.getDeathMessage(); ++ ++ if (deathMessage != null && deathMessage.length() > 0 && flag) { // TODO: allow plugins to override? ++ if (deathMessage.equals(deathmessage)) { ++ Team scoreboardteambase = this.getTeam(); ++ ++ if (scoreboardteambase != null && scoreboardteambase.getDeathMessageVisibility() != Team.EnumVisible.ALWAYS) { ++ if (scoreboardteambase.getDeathMessageVisibility() == Team.EnumVisible.HIDE_FOR_OTHER_TEAMS) { ++ this.mcServer.getPlayerList().sendMessageToAllTeamMembers(this, chatmessage); ++ } else if (scoreboardteambase.getDeathMessageVisibility() == Team.EnumVisible.HIDE_FOR_OWN_TEAM) { ++ this.mcServer.getPlayerList().sendMessageToTeamOrAllPlayers(this, chatmessage); ++ } ++ } else { ++ this.mcServer.getPlayerList().sendMessage(chatmessage); + } +- else if (team.getDeathMessageVisibility() == Team.EnumVisible.HIDE_FOR_OWN_TEAM) +- { +- this.mcServer.getPlayerList().sendMessageToTeamOrAllPlayers(this, this.getCombatTracker().getDeathMessage()); +- } + } + else + { +- this.mcServer.getPlayerList().sendMessage(this.getCombatTracker().getDeathMessage()); ++ // this.mcServer.getPlayerList().sendMessage(this.getCombatTracker().getDeathMessage()); ++ this.mcServer.getPlayerList().sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(deathMessage)); + } + } + + this.spawnShoulderEntities(); ++ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. ++ if (!deathEvent.getKeepInventory()) { ++ this.inventory.clear(); ++ } + + if (!this.world.getGameRules().getBoolean("keepInventory") && !this.isSpectator()) + { +@@ -516,9 +649,14 @@ + } + } + +- for (ScoreObjective scoreobjective : this.world.getScoreboard().getObjectivesFromCriteria(IScoreCriteria.DEATH_COUNT)) ++ this.closeScreen(); ++ this.setSpectatingEntity(this); // Remove spectated target ++ ++ // CraftBukkit - Get our scores instead ++ Collection collection = this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreCriteria.DEATH_COUNT, this.getName(), new java.util.ArrayList()); ++ for (Score score : collection) + { +- Score score = this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective); ++ // Score score = this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective); + score.incrementScore(); + } + +@@ -549,23 +687,28 @@ + { + super.awardKillScore(p_191956_1_, p_191956_2_, p_191956_3_); + this.addScore(p_191956_2_); +- Collection collection = this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.TOTAL_KILL_COUNT); +- ++ // CraftBukkit - Get our scores instead ++ // Collection collection = this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.TOTAL_KILL_COUNT); ++ Collection collection = this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreCriteria.TOTAL_KILL_COUNT, this.getName(), new java.util.ArrayList()); + if (p_191956_1_ instanceof EntityPlayer) + { + this.addStat(StatList.PLAYER_KILLS); +- collection.addAll(this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.PLAYER_KILL_COUNT)); ++ // CraftBukkit - Get our scores instead ++ // collection.addAll(this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.PLAYER_KILL_COUNT)); ++ this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreCriteria.PLAYER_KILL_COUNT, this.getName(), collection); ++ // CraftBukkit end + } + else + { + this.addStat(StatList.MOB_KILLS); + } + +- collection.addAll(this.awardTeamKillScores(p_191956_1_)); ++ collection.addAll(this.awardTeamKillScores_CB(p_191956_1_)); + +- for (ScoreObjective scoreobjective : collection) ++ for (Score score : collection) + { +- this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective).incrementScore(); ++ // this.getWorldScoreboard().getOrCreateScore(this.getName(), scoreobjective).incrementScore(); ++ score.incrementScore(); + } + + CriteriaTriggers.PLAYER_KILLED_ENTITY.trigger(this, p_191956_1_, p_191956_3_); +@@ -606,6 +749,43 @@ + return Lists.newArrayList(); + } + ++ private Collection awardTeamKillScores_CB(Entity p_192038_1_) ++ { ++ String s = p_192038_1_ instanceof EntityPlayer ? p_192038_1_.getName() : p_192038_1_.getCachedUniqueIdString(); ++ ScorePlayerTeam scoreplayerteam = this.getWorldScoreboard().getPlayersTeam(this.getName()); ++ ++ if (scoreplayerteam != null) ++ { ++ int i = scoreplayerteam.getColor().getColorIndex(); ++ ++ if (i >= 0 && i < IScoreCriteria.KILLED_BY_TEAM.length) ++ { ++ for (ScoreObjective scoreobjective : this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.KILLED_BY_TEAM[i])) ++ { ++ Score score = this.getWorldScoreboard().getOrCreateScore(s, scoreobjective); ++ score.incrementScore(); ++ } ++ } ++ } ++ ++ ScorePlayerTeam scoreplayerteam1 = this.getWorldScoreboard().getPlayersTeam(s); ++ ++ if (scoreplayerteam1 != null) ++ { ++ int j = scoreplayerteam1.getColor().getColorIndex(); ++ ++ if (j >= 0 && j < IScoreCriteria.TEAM_KILL.length) ++ { ++ // CraftBukkit - Get our scores instead ++ // return this.getWorldScoreboard().getObjectivesFromCriteria(IScoreCriteria.TEAM_KILL[j]); ++ return this.world.getServer().getScoreboardManager().getScoreboardScores(IScoreCriteria.TEAM_KILL[j], this.getName(), new java.util.ArrayList()); ++ // CraftBukkit end ++ } ++ } ++ ++ return Lists.newArrayList(); ++ } ++ + public boolean attackEntityFrom(DamageSource source, float amount) + { + if (this.isEntityInvulnerable(source)) +@@ -654,14 +834,17 @@ + + private boolean canPlayersAttack() + { +- return this.mcServer.isPVPEnabled(); ++ // CraftBukkit - this.mcServer.isPVPEnabled() -> this.world.pvpMode ++ // return this.mcServer.isPVPEnabled(); ++ return this.world.pvpMode; + } + + @Nullable + public Entity changeDimension(int dimensionIn, net.minecraftforge.common.util.ITeleporter teleporter) + { + if (!net.minecraftforge.common.ForgeHooks.onTravelToDimension(this, dimensionIn)) return this; +- this.invulnerableDimensionChange = true; ++ if (this.isPlayerSleeping()) return this; // CraftBukkit - SPIGOT-3154 ++ // this.invulnerableDimensionChange = true; // CraftBukkit - Moved down and into PlayerList#changeDimension + + if (this.dimension == 0 && dimensionIn == -1) + { +@@ -674,6 +857,7 @@ + + if (this.dimension == 1 && dimensionIn == 1 && teleporter.isVanilla()) + { ++ this.invulnerableDimensionChange = true; // CraftBukkit - Moved down from above + this.world.removeEntity(this); + + if (!this.queuedEndExit) +@@ -692,7 +876,9 @@ + dimensionIn = 1; + } + +- this.mcServer.getPlayerList().transferPlayerToDimension(this, dimensionIn, teleporter); ++ // this.mcServer.getPlayerList().transferPlayerToDimension(this, dimensionIn, teleporter); ++ PlayerTeleportEvent.TeleportCause cause = (this.dimension == 1 || dimensionIn == 1) ? PlayerTeleportEvent.TeleportCause.END_PORTAL : PlayerTeleportEvent.TeleportCause.NETHER_PORTAL; ++ this.mcServer.getPlayerList().changeDimension(this, dimensionIn, cause); // check all this + this.connection.sendPacket(new SPacketEffect(1032, BlockPos.ORIGIN, 0, false)); + this.lastExperience = -1; + this.lastHealth = -1.0F; +@@ -732,11 +918,11 @@ + this.openContainer.detectAndSendChanges(); + } + +- public EntityPlayer.SleepResult trySleep(BlockPos bedLocation) ++ public SleepResult trySleep(BlockPos bedLocation) + { +- EntityPlayer.SleepResult entityplayer$sleepresult = super.trySleep(bedLocation); ++ SleepResult entityplayer$sleepresult = super.trySleep(bedLocation); + +- if (entityplayer$sleepresult == EntityPlayer.SleepResult.OK) ++ if (entityplayer$sleepresult == SleepResult.OK) + { + this.addStat(StatList.SLEEP_IN_BED); + Packet packet = new SPacketUseBed(this, bedLocation); +@@ -751,6 +937,7 @@ + + public void wakeUpPlayer(boolean immediately, boolean updateWorldFlag, boolean setSpawn) + { ++ if (!this.sleeping) return; // CraftBukkit - Can't leave bed if not in one! + if (this.isPlayerSleeping()) + { + this.getServerWorld().getEntityTracker().sendToTrackingAndSelf(this, new SPacketAnimation(this, 2)); +@@ -844,6 +1031,12 @@ + this.connection.sendPacket(new SPacketSignEditorOpen(signTile.getPos())); + } + ++ public int getNextWindowIdCB() // CraftBukkit - void -> int ++ { ++ this.currentWindowId = this.currentWindowId % 100 + 1; ++ return this.currentWindowId; ++ } ++ + public void getNextWindowId() + { + this.currentWindowId = this.currentWindowId % 100 + 1; +@@ -851,15 +1044,23 @@ + + public void displayGui(IInteractionObject guiOwner) + { +- if (guiOwner instanceof ILootContainer && ((ILootContainer)guiOwner).getLootTable() != null && this.isSpectator()) ++ // CraftBukkit start - Inventory open hook ++ if (false && guiOwner instanceof ILootContainer && ((ILootContainer)guiOwner).getLootTable() != null && this.isSpectator()) + { + this.sendStatusMessage((new TextComponentTranslation("container.spectatorCantOpen", new Object[0])).setStyle((new Style()).setColor(TextFormatting.RED)), true); + } + else + { ++ boolean cancelled = guiOwner instanceof ILootContainer && ((ILootContainer) guiOwner).getLootTable() != null && this.isSpectator(); ++ Container container = CraftEventFactory.callInventoryOpenEvent(this, guiOwner.createContainer(this.inventory, this), cancelled); ++ if (container == null) { ++ return; ++ } + this.getNextWindowId(); ++ this.openContainer = container; + this.connection.sendPacket(new SPacketOpenWindow(this.currentWindowId, guiOwner.getGuiID(), guiOwner.getDisplayName())); +- this.openContainer = guiOwner.createContainer(this.inventory, this); ++ // this.openContainer = guiOwner.createContainer(this.inventory, this); ++ // CraftBukkit end + this.openContainer.windowId = this.currentWindowId; + this.openContainer.addListener(this); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.player.PlayerContainerEvent.Open(this, this.openContainer)); +@@ -868,6 +1069,29 @@ + + public void displayGUIChest(IInventory chestInventory) + { ++ // CraftBukkit start - Inventory open hook ++ // Copied from below ++ boolean cancelled = false; ++ if (chestInventory instanceof ILockableContainer) { ++ ILockableContainer itileinventory = (ILockableContainer) chestInventory; ++ cancelled = itileinventory.isLocked() && !this.canOpen(itileinventory.getLockCode()) && !this.isSpectator(); ++ } ++ ++ Container container; ++ if (chestInventory instanceof IInteractionObject) { ++ if (chestInventory instanceof TileEntity) { ++ Preconditions.checkArgument(((TileEntity) chestInventory).getWorld() != null, "Container must have world to be opened"); ++ } ++ container = ((IInteractionObject) chestInventory).createContainer(this.inventory, this); ++ } else { ++ container = new ContainerChest(this.inventory, chestInventory, this); ++ } ++ container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled); ++ if (container == null && !cancelled) { // Let pre-cancelled events fall through ++ chestInventory.closeInventory(this); ++ return; ++ } ++ // CraftBukkit end + if (chestInventory instanceof ILootContainer && ((ILootContainer)chestInventory).getLootTable() != null && this.isSpectator()) + { + this.sendStatusMessage((new TextComponentTranslation("container.spectatorCantOpen", new Object[0])).setStyle((new Style()).setColor(TextFormatting.RED)), true); +@@ -887,8 +1111,10 @@ + { + this.connection.sendPacket(new SPacketChat(new TextComponentTranslation("container.isLocked", new Object[] {chestInventory.getDisplayName()}), ChatType.GAME_INFO)); + this.connection.sendPacket(new SPacketSoundEffect(SoundEvents.BLOCK_CHEST_LOCKED, SoundCategory.BLOCKS, this.posX, this.posY, this.posZ, 1.0F, 1.0F)); ++ ilockablecontainer.closeInventory(this); + return; + } ++ // CraftBukkit end + } + + this.getNextWindowId(); +@@ -896,12 +1122,14 @@ + if (chestInventory instanceof IInteractionObject) + { + this.connection.sendPacket(new SPacketOpenWindow(this.currentWindowId, ((IInteractionObject)chestInventory).getGuiID(), chestInventory.getDisplayName(), chestInventory.getSizeInventory())); +- this.openContainer = ((IInteractionObject)chestInventory).createContainer(this.inventory, this); ++ // this.openContainer = ((IInteractionObject)chestInventory).createContainer(this.inventory, this); ++ this.openContainer = container; + } + else + { + this.connection.sendPacket(new SPacketOpenWindow(this.currentWindowId, "minecraft:container", chestInventory.getDisplayName(), chestInventory.getSizeInventory())); +- this.openContainer = new ContainerChest(this.inventory, chestInventory, this); ++ // this.openContainer = new ContainerChest(this.inventory, chestInventory, this); ++ this.openContainer = container; + } + + this.openContainer.windowId = this.currentWindowId; +@@ -912,8 +1140,15 @@ + + public void displayVillagerTradeGui(IMerchant villager) + { ++ // CraftBukkit start - Inventory open hook ++ Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerMerchant(this.inventory, villager, this.world)); ++ if (container == null) { ++ return; ++ } ++ // CraftBukkit end + this.getNextWindowId(); +- this.openContainer = new ContainerMerchant(this.inventory, villager, this.world); ++ // this.openContainer = new ContainerMerchant(this.inventory, villager, this.world); ++ this.openContainer = container; + this.openContainer.windowId = this.currentWindowId; + this.openContainer.addListener(this); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.player.PlayerContainerEvent.Open(this, this.openContainer)); +@@ -933,6 +1168,13 @@ + + public void openGuiHorseInventory(AbstractHorse horse, IInventory inventoryIn) + { ++ // CraftBukkit start - Inventory open hook ++ Container container = CraftEventFactory.callInventoryOpenEvent(this, new ContainerHorseInventory(this.inventory, inventoryIn, horse, this)); ++ if (container == null) { ++ inventoryIn.closeInventory(this); ++ return; ++ } ++ // CraftBukkit end + if (this.openContainer != this.inventoryContainer) + { + this.closeScreen(); +@@ -940,7 +1182,8 @@ + + this.getNextWindowId(); + this.connection.sendPacket(new SPacketOpenWindow(this.currentWindowId, "EntityHorse", inventoryIn.getDisplayName(), inventoryIn.getSizeInventory(), horse.getEntityId())); +- this.openContainer = new ContainerHorseInventory(this.inventory, inventoryIn, horse, this); ++ // this.openContainer = new ContainerHorseInventory(this.inventory, inventoryIn, horse, this); ++ this.openContainer = container; + this.openContainer.windowId = this.currentWindowId; + this.openContainer.addListener(this); + } +@@ -988,6 +1231,11 @@ + { + this.connection.sendPacket(new SPacketWindowItems(containerToSend.windowId, itemsList)); + this.connection.sendPacket(new SPacketSetSlot(-1, -1, this.inventory.getItemStack())); ++ // CraftBukkit start - Send a Set Slot to update the crafting result slot ++ if (java.util.EnumSet.of(InventoryType.CRAFTING,InventoryType.WORKBENCH).contains(containerToSend.getBukkitView().getType())) { ++ this.connection.sendPacket(new SPacketSetSlot(containerToSend.windowId, 0, containerToSend.getSlot(0).getStack())); ++ } ++ // CraftBukkit end + } + + public void sendWindowProperty(Container containerIn, int varToUpdate, int newValue) +@@ -1005,6 +1253,7 @@ + + public void closeScreen() + { ++ CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit + this.connection.sendPacket(new SPacketCloseWindow(this.openContainer.windowId)); + this.closeContainer(); + } +@@ -1080,6 +1329,10 @@ + + for (ResourceLocation resourcelocation : p_193102_1_) + { ++ if (CraftingManager.getRecipe(resourcelocation) == null) { ++ Bukkit.getLogger().warning("Ignoring grant of non existent recipe " + resourcelocation); ++ continue; ++ } + list.add(CraftingManager.getRecipe(resourcelocation)); + } + +@@ -1110,8 +1363,16 @@ + public void setPlayerHealthUpdated() + { + this.lastHealth = -1.0E8F; ++ this.lastExperience = -1; // CraftBukkit - Added to reset + } + ++ // CraftBukkit start - Support multi-line messages ++ public void sendMessage(ITextComponent[] ichatbasecomponent) { ++ for (ITextComponent component : ichatbasecomponent) { ++ this.sendMessage(component); ++ } ++ } ++ + public void sendStatusMessage(ITextComponent chatComponent, boolean actionBar) + { + this.connection.sendPacket(new SPacketChat(chatComponent, actionBar ? ChatType.GAME_INFO : ChatType.CHAT)); +@@ -1156,7 +1417,7 @@ + this.lastExperience = -1; + this.lastHealth = -1.0F; + this.lastFoodLevel = -1; +- this.recipeBook.copyFrom(that.recipeBook); ++ // this.recipeBook.copyFrom(that.recipeBook); // CraftBukkit + this.entityRemoveQueue.addAll(that.entityRemoveQueue); + this.seenCredits = that.seenCredits; + this.enteredNetherPosition = that.enteredNetherPosition; +@@ -1246,6 +1507,18 @@ + + public void setGameType(GameType gameType) + { ++ // CraftBukkit start ++ if (gameType == this.interactionManager.getGameType()) { ++ return; ++ } ++ ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(getBukkitEntity(), GameMode.getByValue(gameType.getID())); ++ world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ + this.interactionManager.setGameType(gameType); + this.connection.sendPacket(new SPacketChangeGameState(3, (float)gameType.getID())); + +@@ -1280,34 +1553,32 @@ + + public boolean canUseCommand(int permLevel, String commandName) + { +- if ("seed".equals(commandName) && !this.mcServer.isDedicatedServer()) +- { +- return true; ++ if ("@".equals(commandName)) { ++ return getBukkitEntity().hasPermission("minecraft.command.selector"); + } +- else if (!"tell".equals(commandName) && !"help".equals(commandName) && !"me".equals(commandName) && !"trigger".equals(commandName)) +- { +- if (this.mcServer.getPlayerList().canSendCommands(this.getGameProfile())) +- { +- UserListOpsEntry userlistopsentry = (UserListOpsEntry)this.mcServer.getPlayerList().getOppedPlayers().getEntry(this.getGameProfile()); ++ if ("".equals(commandName)) { ++ return getBukkitEntity().isOp(); ++ } ++ return getBukkitEntity().hasPermission("minecraft.command." + commandName); + +- if (userlistopsentry != null) +- { +- return userlistopsentry.getPermissionLevel() >= permLevel; +- } +- else +- { +- return this.mcServer.getOpPermissionLevel() >= permLevel; +- } +- } +- else +- { +- return false; +- } ++ // CraftBukkit end ++ } ++ ++ public boolean canUseCommand(int permLevel, String commandName, String perm) ++ { ++ if ("@".equals(commandName)) { ++ return getBukkitEntity().hasPermission("minecraft.command.selector"); + } +- else ++ if ("".equals(commandName)) { ++ return getBukkitEntity().isOp(); ++ } ++ if (!perm.equals("minecraft")) + { +- return true; ++ return getBukkitEntity().hasPermission(perm); + } ++ return getBukkitEntity().hasPermission("minecraft.command." + commandName); ++ ++ // CraftBukkit end + } + + public String getPlayerIP() +@@ -1320,6 +1591,17 @@ + + public void handleClientSettings(CPacketClientSettings packetIn) + { ++ // CraftBukkit start ++ if (getPrimaryHand() != packetIn.getMainHand()) { ++ PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getPrimaryHand() == EnumHandSide.LEFT ? MainHand.LEFT : MainHand.RIGHT); ++ this.mcServer.server.getPluginManager().callEvent(event); ++ } ++ if (!this.language.equals(packetIn.getLang())) { ++ PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(getBukkitEntity(), packetIn.getLang()); ++ this.mcServer.server.getPluginManager().callEvent(event); ++ } ++ // CraftBukkit end ++ + this.language = packetIn.getLang(); + this.chatVisibility = packetIn.getChatVisibility(); + this.chatColours = packetIn.isColorsEnabled(); +@@ -1327,7 +1609,7 @@ + this.getDataManager().set(MAIN_HAND, Byte.valueOf((byte)(packetIn.getMainHand() == EnumHandSide.LEFT ? 0 : 1))); + } + +- public EntityPlayer.EnumChatVisibility getChatVisibility() ++ public EnumChatVisibility getChatVisibility() + { + return this.chatVisibility; + } +@@ -1402,7 +1684,7 @@ + if (entity != this.spectatingEntity) + { + this.connection.sendPacket(new SPacketCamera(this.spectatingEntity)); +- this.setPositionAndUpdate(this.spectatingEntity.posX, this.spectatingEntity.posY, this.spectatingEntity.posZ); ++ this.connection.setPlayerLocation(this.spectatingEntity.posX, this.spectatingEntity.posY, this.spectatingEntity.posZ, this.rotationYaw, this.rotationPitch, PlayerTeleportEvent.TeleportCause.SPECTATE); + } + } + +@@ -1434,7 +1716,8 @@ + @Nullable + public ITextComponent getTabListDisplayName() + { +- return null; ++ // return null; ++ return listName; // CraftBukkit + } + + public void swingArm(EnumHand hand) +@@ -1455,13 +1738,20 @@ + + public void setElytraFlying() + { +- this.setFlag(7, true); ++ // CraftBukkit start ++ if (!CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) ++ this.setFlag(7, true); ++ // CraftBukkit end + } + + public void clearElytraFlying() + { +- this.setFlag(7, true); +- this.setFlag(7, false); ++ // CraftBukkit start ++ if (!CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) { ++ this.setFlag(7, true); ++ this.setFlag(7, false); ++ } ++ // CraftBukkit end + } + + public PlayerAdvancements getAdvancements() +@@ -1474,4 +1764,145 @@ + { + return this.enteredNetherPosition; + } ++ ++ // CraftBukkit start - Add per-player time and weather. ++ public long timeOffset = 0; ++ public boolean relativeTime = true; ++ ++ public long getPlayerTime() { ++ if (this.relativeTime) { ++ // Adds timeOffset to the current server time. ++ return this.world.getWorldTime() + this.timeOffset; ++ } else { ++ // Adds timeOffset to the beginning of this day. ++ return this.world.getWorldTime() - (this.world.getWorldTime() % 24000) + this.timeOffset; ++ } ++ } ++ ++ public WeatherType weather = null; ++ ++ public WeatherType getPlayerWeather() { ++ return this.weather; ++ } ++ ++ public void setPlayerWeather(WeatherType type, boolean plugin) { ++ if (!plugin && this.weather != null) { ++ return; ++ } ++ ++ if (plugin) { ++ this.weather = type; ++ } ++ ++ if (type == WeatherType.DOWNFALL) { ++ this.connection.sendPacket(new SPacketChangeGameState(2, 0)); ++ } else { ++ this.connection.sendPacket(new SPacketChangeGameState(1, 0)); ++ } ++ } ++ ++ private float pluginRainPosition; ++ private float pluginRainPositionPrevious; ++ ++ public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) { ++ if (this.weather == null) { ++ // Vanilla ++ if (oldRain != newRain) { ++ this.connection.sendPacket(new SPacketChangeGameState(7, newRain)); ++ } ++ } else { ++ // Plugin ++ if (pluginRainPositionPrevious != pluginRainPosition) { ++ this.connection.sendPacket(new SPacketChangeGameState(7, pluginRainPosition)); ++ } ++ } ++ ++ if (oldThunder != newThunder) { ++ if (weather == WeatherType.DOWNFALL || weather == null) { ++ this.connection.sendPacket(new SPacketChangeGameState(8, newThunder)); ++ } else { ++ this.connection.sendPacket(new SPacketChangeGameState(8, 0)); ++ } ++ } ++ } ++ ++ public void tickWeather() { ++ if (this.weather == null) return; ++ ++ pluginRainPositionPrevious = pluginRainPosition; ++ if (weather == WeatherType.DOWNFALL) { ++ pluginRainPosition += 0.01; ++ } else { ++ pluginRainPosition -= 0.01; ++ } ++ ++ pluginRainPosition = MathHelper.clamp(pluginRainPosition, 0.0F, 1.0F); ++ } ++ ++ public void resetPlayerWeather() { ++ this.weather = null; ++ this.setPlayerWeather(this.world.getWorldInfo().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false); ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + "(" + this.getName() + " at " + this.posX + "," + this.posY + "," + this.posZ + ")"; ++ } ++ ++ // SPIGOT-1903, MC-98153 ++ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) { ++ this.setLocationAndAngles(x, y, z, yaw, pitch); ++ this.connection.captureCurrentPosition(); ++ } ++ ++ @Override ++ public boolean isMovementBlocked() { ++ return super.isMovementBlocked() || !getBukkitEntity().isOnline(); ++ } ++ ++ @Override ++ public Scoreboard getWorldScoreboard() { ++ return getBukkitEntity().getScoreboard().getHandle(); ++ } ++ ++ public void reset() { ++ float exp = 0; ++ boolean keepInventory = this.world.getGameRules().getBoolean("keepInventory"); ++ ++ if (this.keepLevel || keepInventory) { ++ exp = this.experience; ++ this.newTotalExp = this.experienceTotal; ++ this.newLevel = this.experienceLevel; ++ } ++ ++ this.setHealth(this.getMaxHealth()); ++ this.fire = 0; ++ this.fallDistance = 0; ++ this.foodStats = new FoodStats(); ++ this.foodStats.player = this; ++ this.experienceLevel = this.newLevel; ++ this.experienceTotal = this.newTotalExp; ++ this.experience = 0; ++ this.deathTime = 0; ++ this.setArrowCountInEntity(0); ++ this.clearActivePotions(); ++ this.potionsNeedUpdate = true; ++ this.openContainer = this.inventoryContainer; ++ this.attackingPlayer = null; ++ this.revengeTarget = null; ++ this._combatTracker = new CombatTracker(this); ++ this.lastExperience = -1; ++ if (this.keepLevel || keepInventory) { ++ this.experience = exp; ++ } else { ++ this.addExperience(this.newExp); ++ } ++ this.keepLevel = false; ++ getEntityData().getKeySet().removeIf(tag -> !PERSISTED_NBT_TAG.equals(tag)); ++ } ++ ++ @Override ++ public CraftPlayer getBukkitEntity() { ++ return (CraftPlayer) super.getBukkitEntity(); ++ } + } diff --git a/patches/net/minecraft/entity/player/InventoryPlayer.java.patch b/patches/net/minecraft/entity/player/InventoryPlayer.java.patch new file mode 100644 index 00000000..8097fe8c --- /dev/null +++ b/patches/net/minecraft/entity/player/InventoryPlayer.java.patch @@ -0,0 +1,115 @@ +--- ../src-base/minecraft/net/minecraft/entity/player/InventoryPlayer.java ++++ ../src-work/minecraft/net/minecraft/entity/player/InventoryPlayer.java +@@ -1,5 +1,6 @@ + package net.minecraft.entity.player; + ++import java.util.ArrayList; + import java.util.Arrays; + import java.util.Iterator; + import java.util.List; +@@ -26,6 +27,9 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + + public class InventoryPlayer implements IInventory + { +@@ -38,6 +42,50 @@ + private ItemStack itemStack; + private int timesChanged; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ List combined = new ArrayList<>(mainInventory.size() + armorInventory.size() + offHandInventory.size()); ++ for (List sub : this.allInventories) { ++ combined.addAll(sub); ++ } ++ ++ return combined; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public List getArmorContents() { ++ return this.armorInventory; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return this.player.getBukkitEntity(); ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return player.getBukkitEntity().getLocation(); ++ } ++ ++ // CraftBukkit end ++ + public InventoryPlayer(EntityPlayer playerIn) + { + this.allInventories = Arrays.>asList(this.mainInventory, this.armorInventory, this.offHandInventory); +@@ -65,6 +113,23 @@ + return stack1.getItem() == stack2.getItem() && (!stack1.getHasSubtypes() || stack1.getMetadata() == stack2.getMetadata()) && ItemStack.areItemStackTagsEqual(stack1, stack2); + } + ++ // CraftBukkit start - Watch method above! :D ++ public int canHold(ItemStack itemstack) { ++ int remains = itemstack.getCount(); ++ for (int i = 0; i < this.mainInventory.size(); ++i) { ++ ItemStack itemstack1 = this.getStackInSlot(i); ++ if (itemstack1.isEmpty()) return itemstack.getCount(); ++ ++ // Taken from firstPartial(ItemStack) ++ if (!itemstack1.isEmpty() && itemstack1.getItem() == itemstack.getItem() && itemstack1.isStackable() && itemstack1.getCount() < itemstack1.getMaxStackSize() && itemstack1.getCount() < this.getInventoryStackLimit() && (!itemstack1.getHasSubtypes() || itemstack1.getMetadata() == itemstack.getMetadata()) && ItemStack.areItemStackTagsEqual(itemstack1, itemstack)) { ++ remains -= (itemstack1.getMaxStackSize() < this.getInventoryStackLimit() ? itemstack1.getMaxStackSize() : this.getInventoryStackLimit()) - itemstack1.getCount(); ++ } ++ if (remains <= 0) return itemstack.getCount(); ++ } ++ return itemstack.getCount() - remains; ++ } ++ // CraftBukkit end ++ + public int getFirstEmptyStack() + { + for (int i = 0; i < this.mainInventory.size(); ++i) +@@ -725,7 +790,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; // CraftBukkit + } + + public boolean canHarvestBlock(IBlockState state) +@@ -802,6 +867,11 @@ + + public ItemStack getItemStack() + { ++ // CraftBukkit start ++ if (this.itemStack.isEmpty()) { ++ this.setItemStack(ItemStack.EMPTY); ++ } ++ // CraftBukkit end + return this.itemStack; + } + diff --git a/patches/net/minecraft/entity/player/PlayerCapabilities.java.patch b/patches/net/minecraft/entity/player/PlayerCapabilities.java.patch new file mode 100644 index 00000000..3bc75943 --- /dev/null +++ b/patches/net/minecraft/entity/player/PlayerCapabilities.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/entity/player/PlayerCapabilities.java ++++ ../src-work/minecraft/net/minecraft/entity/player/PlayerCapabilities.java +@@ -11,8 +11,8 @@ + public boolean allowFlying; + public boolean isCreativeMode; + public boolean allowEdit = true; +- private float flySpeed = 0.05F; +- private float walkSpeed = 0.1F; ++ public float flySpeed = 0.05F; ++ public float walkSpeed = 0.1F; + + public void writeCapabilitiesToNBT(NBTTagCompound tagCompound) + { diff --git a/patches/net/minecraft/entity/projectile/EntityArrow.java.patch b/patches/net/minecraft/entity/projectile/EntityArrow.java.patch new file mode 100644 index 00000000..7bc29c73 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityArrow.java.patch @@ -0,0 +1,139 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityArrow.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityArrow.java +@@ -12,6 +12,7 @@ + import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.IProjectile; + import net.minecraft.entity.MoverType; ++import net.minecraft.entity.item.EntityItem; + import net.minecraft.entity.monster.EntityEnderman; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; +@@ -36,6 +37,10 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++import org.bukkit.event.player.PlayerPickupArrowEvent; + + public abstract class EntityArrow extends Entity implements IProjectile + { +@@ -47,21 +52,33 @@ + } + }); + private static final DataParameter CRITICAL = EntityDataManager.createKey(EntityArrow.class, DataSerializers.BYTE); +- private int xTile; +- private int yTile; +- private int zTile; ++ public int xTile; ++ public int yTile; ++ public int zTile; + private Block inTile; + private int inData; +- protected boolean inGround; ++ public boolean inGround; // Spigot + protected int timeInGround; +- public EntityArrow.PickupStatus pickupStatus; ++ public PickupStatus pickupStatus; + public int arrowShake; + public Entity shootingEntity; + private int ticksInGround; + private int ticksInAir; + private double damage; +- private int knockbackStrength; ++ public int knockbackStrength; + ++ // Spigot Start ++ @Override ++ public void inactiveTick() ++ { ++ if ( this.inGround ) ++ { ++ this.ticksInGround += 1; // Despawn counter. First int after shooter ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ + public EntityArrow(World worldIn) + { + super(worldIn); +@@ -83,6 +100,7 @@ + { + this(worldIn, shooter.posX, shooter.posY + (double)shooter.getEyeHeight() - 0.10000000149011612D, shooter.posZ); + this.shootingEntity = shooter; ++ this.projectileSource = (LivingEntity) shooter.getBukkitEntity(); + + if (shooter instanceof EntityPlayer) + { +@@ -222,7 +240,7 @@ + { + ++this.ticksInGround; + +- if (this.ticksInGround >= 1200) ++ if (this.ticksInGround >= world.spigotConfig.arrowDespawnRate) // Spigot - First int after shooter + { + this.setDead(); + } +@@ -339,7 +357,7 @@ + protected void onHit(RayTraceResult raytraceResultIn) + { + Entity entity = raytraceResultIn.entityHit; +- ++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, raytraceResultIn); + if (entity != null) + { + float f = MathHelper.sqrt(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ); +@@ -363,7 +381,12 @@ + + if (this.isBurning() && !(entity instanceof EntityEnderman)) + { +- entity.setFire(5); ++// entity.setFire(5); ++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5); ++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent); ++ if (!combustEvent.isCancelled()) { ++ entity.setFire(combustEvent.getDuration()); ++ } + } + + if (entity.attackEntityFrom(damagesource, (float)i)) +@@ -570,9 +593,20 @@ + { + if (!this.world.isRemote && this.inGround && this.arrowShake <= 0) + { +- boolean flag = this.pickupStatus == EntityArrow.PickupStatus.ALLOWED || this.pickupStatus == EntityArrow.PickupStatus.CREATIVE_ONLY && entityIn.capabilities.isCreativeMode; ++ ItemStack itemstack = this.getArrowStack(); ++ EntityItem item = new EntityItem(this.world, this.posX, this.posY, this.posZ, itemstack); ++ if (this.pickupStatus == PickupStatus.ALLOWED && entityIn.inventory.canHold(itemstack) > 0) { ++ PlayerPickupArrowEvent event = new PlayerPickupArrowEvent((org.bukkit.entity.Player) entityIn.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.world.getServer(), this, item), (org.bukkit.entity.Arrow) this.getBukkitEntity()); ++ // event.setCancelled(!entityhuman.thisisatest); TODO ++ this.world.getServer().getPluginManager().callEvent(event); + +- if (this.pickupStatus == EntityArrow.PickupStatus.ALLOWED && !entityIn.inventory.addItemStackToInventory(this.getArrowStack())) ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ boolean flag = this.pickupStatus == PickupStatus.ALLOWED || this.pickupStatus == PickupStatus.CREATIVE_ONLY && entityIn.capabilities.isCreativeMode; ++ ++ if (this.pickupStatus == PickupStatus.ALLOWED && !entityIn.inventory.addItemStackToInventory(item.getItem())) + { + flag = false; + } +@@ -655,7 +689,13 @@ + + if (EnchantmentHelper.getMaxEnchantmentLevel(Enchantments.FLAME, p_190547_1_) > 0) + { +- this.setFire(100); ++// this.setFire(100); ++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), 100); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setFire(event.getDuration()); ++ } + } + } + diff --git a/patches/net/minecraft/entity/projectile/EntityEgg.java.patch b/patches/net/minecraft/entity/projectile/EntityEgg.java.patch new file mode 100644 index 00000000..c6737c62 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityEgg.java.patch @@ -0,0 +1,76 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityEgg.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityEgg.java +@@ -1,7 +1,8 @@ + package net.minecraft.entity.projectile; + ++import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityLivingBase; +-import net.minecraft.entity.passive.EntityChicken; ++import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.init.Items; + import net.minecraft.item.Item; + import net.minecraft.util.DamageSource; +@@ -11,6 +12,10 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerEggThrowEvent; + + public class EntityEgg extends EntityThrowable + { +@@ -57,22 +62,44 @@ + + if (!this.world.isRemote) + { +- if (this.rand.nextInt(8) == 0) ++ boolean hatching = this.rand.nextInt(8) == 0; ++ if (/*this.rand.nextInt(8) == 0*/ true) + { +- int i = 1; ++ byte i = 1; + + if (this.rand.nextInt(32) == 0) + { + i = 4; + } + +- for (int j = 0; j < i; ++j) +- { +- EntityChicken entitychicken = new EntityChicken(this.world); +- entitychicken.setGrowingAge(-24000); +- entitychicken.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F); +- this.world.spawnEntity(entitychicken); ++// for (int j = 0; j < i; ++j) { ++// EntityChicken entitychicken = new EntityChicken(this.world); ++ if (!hatching) { ++ i = 0; + } ++ EntityType hatchingType = EntityType.CHICKEN; ++ ++ Entity shooter = this.getThrower(); ++ if (shooter instanceof EntityPlayer) { ++ PlayerEggThrowEvent event = new PlayerEggThrowEvent((Player) shooter.getBukkitEntity(), (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, i, hatchingType); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ i = event.getNumHatches(); ++ hatching = event.isHatching(); ++ hatchingType = event.getHatchingType(); ++ } ++// entitychicken.setGrowingAge(-24000); ++// entitychicken.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F); ++// this.world.spawnEntity(entitychicken); ++ if (hatching) { ++ for (int k = 0; k < i; ++k) { ++ Entity entity = world.getWorld().createEntity(new org.bukkit.Location(world.getWorld(), this.posX, this.posY, this.posZ, this.rotationYaw, 0.0F), hatchingType.getEntityClass()); ++ if (entity.getBukkitEntity() instanceof Ageable) { ++ ((Ageable) entity.getBukkitEntity()).setBaby(); ++ } ++ world.getWorld().addEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); ++ } ++ } + } + + this.world.setEntityState(this, (byte)3); diff --git a/patches/net/minecraft/entity/projectile/EntityEvokerFangs.java.patch b/patches/net/minecraft/entity/projectile/EntityEvokerFangs.java.patch new file mode 100644 index 00000000..fa426d2d --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityEvokerFangs.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityEvokerFangs.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityEvokerFangs.java +@@ -136,7 +136,9 @@ + { + if (entitylivingbase == null) + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = this; + p_190551_1_.attackEntityFrom(DamageSource.MAGIC, 6.0F); ++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; + } + else + { diff --git a/patches/net/minecraft/entity/projectile/EntityFireball.java.patch b/patches/net/minecraft/entity/projectile/EntityFireball.java.patch new file mode 100644 index 00000000..32705663 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityFireball.java.patch @@ -0,0 +1,72 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityFireball.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityFireball.java +@@ -14,6 +14,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public abstract class EntityFireball extends Entity + { +@@ -24,6 +25,9 @@ + public double accelerationY; + public double accelerationZ; + ++ public float bukkitYield = 1; // CraftBukkit ++ public boolean isIncendiary = true; // CraftBukkit ++ + public EntityFireball(World worldIn) + { + super(worldIn); +@@ -64,12 +68,19 @@ + { + super(worldIn); + this.shootingEntity = shooter; ++ this.projectileSource = (org.bukkit.entity.LivingEntity) shooter.getBukkitEntity(); // CraftBukkit + this.setSize(1.0F, 1.0F); + this.setLocationAndAngles(shooter.posX, shooter.posY, shooter.posZ, shooter.rotationYaw, shooter.rotationPitch); + this.setPosition(this.posX, this.posY, this.posZ); + this.motionX = 0.0D; + this.motionY = 0.0D; + this.motionZ = 0.0D; ++ // CraftBukkit start - Added setDirection method ++ this.setDirection(accelX, accelY, accelZ); ++ } ++ ++ public void setDirection(double accelX, double accelY, double accelZ) { ++ // CraftBukkit end + accelX = accelX + this.rand.nextGaussian() * 0.4D; + accelY = accelY + this.rand.nextGaussian() * 0.4D; + accelZ = accelZ + this.rand.nextGaussian() * 0.4D; +@@ -96,6 +107,11 @@ + if (raytraceresult != null && !net.minecraftforge.event.ForgeEventFactory.onProjectileImpact(this, raytraceresult)) + { + this.onImpact(raytraceresult); ++ // CraftBukkit start - Fire ProjectileHitEvent ++ if (this.isDead) { ++ CraftEventFactory.callProjectileHitEvent(this, raytraceresult); ++ } ++ // CraftBukkit end + } + + this.posX += this.motionX; +@@ -209,6 +225,11 @@ + + if (source.getTrueSource() != null) + { ++ // CraftBukkit start ++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } ++ // CraftBukkit end + Vec3d vec3d = source.getTrueSource().getLookVec(); + + if (vec3d != null) +@@ -224,6 +245,7 @@ + if (source.getTrueSource() instanceof EntityLivingBase) + { + this.shootingEntity = (EntityLivingBase)source.getTrueSource(); ++ this.projectileSource = (org.bukkit.projectiles.ProjectileSource) this.shootingEntity.getBukkitEntity(); + } + + return true; diff --git a/patches/net/minecraft/entity/projectile/EntityFishHook.java.patch b/patches/net/minecraft/entity/projectile/EntityFishHook.java.patch new file mode 100644 index 00000000..5971405b --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityFishHook.java.patch @@ -0,0 +1,181 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityFishHook.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityFishHook.java +@@ -32,20 +32,23 @@ + import net.minecraft.world.storage.loot.LootTableList; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerFishEvent; + + public class EntityFishHook extends Entity + { + private static final DataParameter DATA_HOOKED_ENTITY = EntityDataManager.createKey(EntityFishHook.class, DataSerializers.VARINT); + private boolean inGround; + private int ticksInGround; +- private EntityPlayer angler; ++ public EntityPlayer angler; + private int ticksInAir; + private int ticksCatchable; + private int ticksCaughtDelay; + private int ticksCatchableDelay; + private float fishApproachAngle; + public Entity caughtEntity; +- private EntityFishHook.State currentState = EntityFishHook.State.FLYING; ++ private State currentState = State.FLYING; + private int luck; + private int lureSpeed; + +@@ -169,14 +172,14 @@ + f = BlockLiquid.getBlockLiquidHeight(iblockstate, this.world, blockpos); + } + +- if (this.currentState == EntityFishHook.State.FLYING) ++ if (this.currentState == State.FLYING) + { + if (this.caughtEntity != null) + { + this.motionX = 0.0D; + this.motionY = 0.0D; + this.motionZ = 0.0D; +- this.currentState = EntityFishHook.State.HOOKED_IN_ENTITY; ++ this.currentState = State.HOOKED_IN_ENTITY; + return; + } + +@@ -185,7 +188,7 @@ + this.motionX *= 0.3D; + this.motionY *= 0.2D; + this.motionZ *= 0.3D; +- this.currentState = EntityFishHook.State.BOBBING; ++ this.currentState = State.BOBBING; + return; + } + +@@ -208,14 +211,14 @@ + } + else + { +- if (this.currentState == EntityFishHook.State.HOOKED_IN_ENTITY) ++ if (this.currentState == State.HOOKED_IN_ENTITY) + { + if (this.caughtEntity != null) + { + if (this.caughtEntity.isDead) + { + this.caughtEntity = null; +- this.currentState = EntityFishHook.State.FLYING; ++ this.currentState = State.FLYING; + } + else + { +@@ -230,7 +233,7 @@ + return; + } + +- if (this.currentState == EntityFishHook.State.BOBBING) ++ if (this.currentState == State.BOBBING) + { + this.motionX *= 0.9D; + this.motionZ *= 0.9D; +@@ -356,6 +359,7 @@ + + if (raytraceresult != null && raytraceresult.typeOfHit != RayTraceResult.Type.MISS) + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, raytraceresult); // Craftbukkit - Call event + if (raytraceresult.typeOfHit == RayTraceResult.Type.ENTITY) + { + this.caughtEntity = raytraceresult.entityHit; +@@ -397,6 +401,10 @@ + { + this.ticksCaughtDelay = 0; + this.ticksCatchableDelay = 0; ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ // CraftBukkit end + } + else + { +@@ -433,6 +441,13 @@ + } + else + { ++ ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.BITE); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ if (playerFishEvent.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.motionY = (double)(-0.4F * MathHelper.nextFloat(this.rand, 0.6F, 1.0F)); + this.playSound(SoundEvents.ENTITY_BOBBER_SPLASH, 0.25F, 1.0F + (this.rand.nextFloat() - this.rand.nextFloat()) * 0.4F); + double d3 = this.getEntityBoundingBox().minY + 0.5D; +@@ -509,6 +524,11 @@ + net.minecraftforge.event.entity.player.ItemFishedEvent event = null; + if (this.caughtEntity != null) + { ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), this.caughtEntity.getBukkitEntity(), (Fish) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } + this.bringInHookedEntity(); + this.world.setEntityState(this, (byte)31); + i = this.caughtEntity instanceof EntityItem ? 3 : 5; +@@ -529,6 +549,15 @@ + for (ItemStack itemstack : result) + { + EntityItem entityitem = new EntityItem(this.world, this.posX, this.posY, this.posZ, itemstack); ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), entityitem.getBukkitEntity(), (Fish) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); ++ playerFishEvent.setExpToDrop(this.rand.nextInt(6) + 1); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ // CraftBukkit end + double d0 = this.angler.posX - this.posX; + double d1 = this.angler.posY - this.posY; + double d2 = this.angler.posZ - this.posZ; +@@ -538,7 +567,12 @@ + entityitem.motionY = d1 * 0.1D + (double)MathHelper.sqrt(d3) * 0.08D; + entityitem.motionZ = d2 * 0.1D; + this.world.spawnEntity(entityitem); +- this.angler.world.spawnEntity(new EntityXPOrb(this.angler.world, this.angler.posX, this.angler.posY + 0.5D, this.angler.posZ + 0.5D, this.rand.nextInt(6) + 1)); ++ // this.angler.world.spawnEntity(new EntityXPOrb(this.angler.world, this.angler.posX, this.angler.posY + 0.5D, this.angler.posZ + 0.5D, this.rand.nextInt(6) + 1)); ++ // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() ++ if (playerFishEvent.getExpToDrop() > 0) { ++ this.angler.world.spawnEntity(new EntityXPOrb(this.angler.world, this.angler.posX, this.angler.posY + 0.5D, this.angler.posZ + 0.5D, playerFishEvent.getExpToDrop())); ++ } ++ // CraftBukkit end + Item item = itemstack.getItem(); + + if (item == Items.FISH || item == Items.COOKED_FISH) +@@ -552,8 +586,25 @@ + + if (this.inGround) + { ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ // CraftBukkit end + i = 2; + } ++ // CraftBukkit start ++ if (i == 0) { ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.angler.getBukkitEntity(), null, (Fish) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT); ++ this.world.getServer().getPluginManager().callEvent(playerFishEvent); ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ } ++ // CraftBukkit end + + this.setDead(); + return event == null ? i : event.getRodDamage(); diff --git a/patches/net/minecraft/entity/projectile/EntityLargeFireball.java.patch b/patches/net/minecraft/entity/projectile/EntityLargeFireball.java.patch new file mode 100644 index 00000000..324a91fc --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityLargeFireball.java.patch @@ -0,0 +1,55 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityLargeFireball.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityLargeFireball.java +@@ -9,6 +9,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityLargeFireball extends EntityFireball + { +@@ -17,6 +18,7 @@ + public EntityLargeFireball(World worldIn) + { + super(worldIn); ++ isIncendiary = this.world.getGameRules().getBoolean("mobGriefing"); + } + + @SideOnly(Side.CLIENT) +@@ -28,6 +30,7 @@ + public EntityLargeFireball(World worldIn, EntityLivingBase shooter, double accelX, double accelY, double accelZ) + { + super(worldIn, shooter, accelX, accelY, accelZ); ++ isIncendiary = this.world.getGameRules().getBoolean("mobGriefing"); + } + + protected void onImpact(RayTraceResult result) +@@ -40,8 +43,17 @@ + this.applyEnchantments(this.shootingEntity, result.entityHit); + } + ++ // TODO: Reimplement with correct `flag` usage below + boolean flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.shootingEntity); +- this.world.newExplosion((Entity)null, this.posX, this.posY, this.posZ, (float)this.explosionPower, flag, flag); ++ // this.world.newExplosion((Entity)null, this.posX, this.posY, this.posZ, (float)this.explosionPower, flag, flag); ++ // CraftBukkit start - fire ExplosionPrimeEvent ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) org.bukkit.craftbukkit.entity.CraftEntity.getEntity(this.world.getServer(), this)); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ // give 'this' instead of (Entity) null so we know what causes the damage ++ this.world.newExplosion(this, this.posX, this.posY, this.posZ, event.getRadius(), event.getFire(), isIncendiary); ++ } ++ // CraftBukkit end + this.setDead(); + } + } +@@ -63,7 +75,8 @@ + + if (compound.hasKey("ExplosionPower", 99)) + { +- this.explosionPower = compound.getInteger("ExplosionPower"); ++ // CraftBukkit - set bukkitYield when setting explosionpower ++ bukkitYield = this.explosionPower = compound.getInteger("ExplosionPower"); + } + } + } diff --git a/patches/net/minecraft/entity/projectile/EntityLlamaSpit.java.patch b/patches/net/minecraft/entity/projectile/EntityLlamaSpit.java.patch new file mode 100644 index 00000000..88d41dc6 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityLlamaSpit.java.patch @@ -0,0 +1,27 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityLlamaSpit.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityLlamaSpit.java +@@ -5,6 +5,7 @@ + import javax.annotation.Nullable; + import net.minecraft.block.material.Material; + import net.minecraft.entity.Entity; ++import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.IProjectile; + import net.minecraft.entity.passive.EntityLlama; + import net.minecraft.nbt.NBTTagCompound; +@@ -20,7 +21,7 @@ + + public class EntityLlamaSpit extends Entity implements IProjectile + { +- public EntityLlama owner; ++ public EntityLivingBase owner; // CraftBukkit - type EntityLlama -> EntityLivingBase + private NBTTagCompound ownerNbt; + + public EntityLlamaSpit(World worldIn) +@@ -211,6 +212,7 @@ + + public void onHit(RayTraceResult p_190536_1_) + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, p_190536_1_); // CraftBukkit + if (p_190536_1_.entityHit != null && this.owner != null) + { + p_190536_1_.entityHit.attackEntityFrom(DamageSource.causeIndirectDamage(this, this.owner).setProjectile(), 1.0F); diff --git a/patches/net/minecraft/entity/projectile/EntityPotion.java.patch b/patches/net/minecraft/entity/projectile/EntityPotion.java.patch new file mode 100644 index 00000000..a984a154 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityPotion.java.patch @@ -0,0 +1,133 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityPotion.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityPotion.java +@@ -1,7 +1,9 @@ + package net.minecraft.entity.projectile; + + import com.google.common.base.Predicate; ++import java.util.HashMap; + import java.util.List; ++import java.util.Map; + import javax.annotation.Nullable; + import net.minecraft.entity.EntityAreaEffectCloud; + import net.minecraft.entity.EntityLivingBase; +@@ -31,6 +33,8 @@ + import net.minecraft.world.World; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; + + public class EntityPotion extends EntityThrowable + { +@@ -124,7 +128,7 @@ + { + this.applyWater(); + } +- else if (!list.isEmpty()) ++ else if (true || !list.isEmpty()) // CraftBukkit - Call event even if no effects to apply + { + if (this.isLingering()) + { +@@ -161,45 +165,55 @@ + } + } + +- private void applySplash(RayTraceResult p_190543_1_, List p_190543_2_) +- { ++ private void applySplash(RayTraceResult p_190543_1_, List p_190543_2_) { + AxisAlignedBB axisalignedbb = this.getEntityBoundingBox().grow(4.0D, 2.0D, 4.0D); + List list = this.world.getEntitiesWithinAABB(EntityLivingBase.class, axisalignedbb); +- +- if (!list.isEmpty()) +- { +- for (EntityLivingBase entitylivingbase : list) +- { +- if (entitylivingbase.canBeHitWithPotion()) +- { ++ Map affected = new HashMap(); ++ if (!list.isEmpty()) { ++ for (EntityLivingBase entitylivingbase : list) { ++ if (entitylivingbase.canBeHitWithPotion()) { + double d0 = this.getDistanceSq(entitylivingbase); + +- if (d0 < 16.0D) +- { ++ if (d0 < 16.0D) { + double d1 = 1.0D - Math.sqrt(d0) / 4.0D; + +- if (entitylivingbase == p_190543_1_.entityHit) +- { ++ if (entitylivingbase == p_190543_1_.entityHit) { + d1 = 1.0D; + } ++ affected.put((LivingEntity) entitylivingbase.getBukkitEntity(), d1); ++ } ++ } ++ } ++ } ++ org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, affected); ++ if (!event.isCancelled() && p_190543_2_ != null && !p_190543_2_.isEmpty()) { // do not process effects if there are no effects to process ++ for (LivingEntity victim : event.getAffectedEntities()) { ++ if (!(victim instanceof CraftLivingEntity)) { ++ continue; ++ } ++ EntityLivingBase entityliving = ((CraftLivingEntity) victim).getHandle(); ++ double d1 = event.getIntensity(victim); ++ // CraftBukkit end + +- for (PotionEffect potioneffect : p_190543_2_) +- { +- Potion potion = potioneffect.getPotion(); ++ for (PotionEffect mobeffect : p_190543_2_) { ++ Potion mobeffectlist = mobeffect.getPotion(); ++ // CraftBukkit start - Abide by PVP settings - for players only! ++ if (!this.world.pvpMode && this.getThrower() instanceof EntityPlayer && entityliving instanceof EntityPlayer && entityliving != this.getThrower()) { ++ int i = Potion.getIdFromPotion(mobeffectlist); ++ // Block SLOWER_MOVEMENT, SLOWER_DIG, HARM, BLINDNESS, HUNGER, WEAKNESS and POISON potions ++ if (i == 2 || i == 4 || i == 7 || i == 15 || i == 17 || i == 18 || i == 19) { ++ continue; ++ } ++ } ++ // CraftBukkit end + +- if (potion.isInstant()) +- { +- potion.affectEntity(this, this.getThrower(), entitylivingbase, potioneffect.getAmplifier(), d1); +- } +- else +- { +- int i = (int)(d1 * (double)potioneffect.getDuration() + 0.5D); ++ if (mobeffectlist.isInstant()) { ++ mobeffectlist.affectEntity(this, this.getThrower(), entityliving, mobeffect.getAmplifier(), d1); ++ } else { ++ int i = (int) (d1 * (double) mobeffect.getDuration() + 0.5D); + +- if (i > 20) +- { +- entitylivingbase.addPotionEffect(new PotionEffect(potion, i, potioneffect.getAmplifier(), potioneffect.getIsAmbient(), potioneffect.doesShowParticles())); +- } +- } ++ if (i > 20) { ++ entityliving.addPotionEffect(new PotionEffect(mobeffectlist, i, mobeffect.getAmplifier(), mobeffect.getIsAmbient(), mobeffect.doesShowParticles())); + } + } + } +@@ -229,10 +243,16 @@ + entityareaeffectcloud.setColor(nbttagcompound.getInteger("CustomPotionColor")); + } + +- this.world.spawnEntity(entityareaeffectcloud); ++ // this.world.spawnEntity(entityareaeffectcloud); ++ org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, entityareaeffectcloud); ++ if (!(event.isCancelled() || entityareaeffectcloud.isDead)) { ++ this.world.spawnEntity(entityareaeffectcloud); ++ } else { ++ entityareaeffectcloud.isDead = true; ++ } + } + +- private boolean isLingering() ++ public boolean isLingering() + { + return this.getPotion().getItem() == Items.LINGERING_POTION; + } diff --git a/patches/net/minecraft/entity/projectile/EntityShulkerBullet.java.patch b/patches/net/minecraft/entity/projectile/EntityShulkerBullet.java.patch new file mode 100644 index 00000000..242dce03 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityShulkerBullet.java.patch @@ -0,0 +1,40 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityShulkerBullet.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityShulkerBullet.java +@@ -77,8 +77,29 @@ + this.target = targetIn; + this.direction = EnumFacing.UP; + this.selectNextMoveDirection(p_i46772_4_); ++ projectileSource = (org.bukkit.entity.LivingEntity) ownerIn.getBukkitEntity(); + } + ++ // CraftBukkit start ++ public EntityLivingBase getShooter() { ++ return this.owner; ++ } ++ ++ public void setShooter(EntityLivingBase e) { ++ this.owner = e; ++ } ++ ++ public Entity getTarget() { ++ return this.target; ++ } ++ ++ public void setTarget(Entity e) { ++ this.target = e; ++ this.direction = EnumFacing.UP; ++ this.selectNextMoveDirection(EnumFacing.Axis.X); ++ } ++ // CraftBukkit end ++ + protected void writeEntityToNBT(NBTTagCompound compound) + { + if (this.owner != null) +@@ -382,6 +403,7 @@ + + protected void bulletHit(RayTraceResult result) + { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, result); // CraftBukkit + if (result.entityHit == null) + { + ((WorldServer)this.world).spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 2, 0.2D, 0.2D, 0.2D, 0.0D); diff --git a/patches/net/minecraft/entity/projectile/EntitySmallFireball.java.patch b/patches/net/minecraft/entity/projectile/EntitySmallFireball.java.patch new file mode 100644 index 00000000..2547d7c3 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntitySmallFireball.java.patch @@ -0,0 +1,61 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntitySmallFireball.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntitySmallFireball.java +@@ -8,6 +8,7 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.RayTraceResult; + import net.minecraft.world.World; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; + + public class EntitySmallFireball extends EntityFireball + { +@@ -21,6 +22,9 @@ + { + super(worldIn, shooter, accelX, accelY, accelZ); + this.setSize(0.3125F, 0.3125F); ++ if (this.shootingEntity != null && this.shootingEntity instanceof EntityLiving) { ++ isIncendiary = this.world.getGameRules().getBoolean("mobGriefing"); ++ } + } + + public EntitySmallFireball(World worldIn, double x, double y, double z, double accelX, double accelY, double accelZ) +@@ -42,12 +46,17 @@ + { + if (!result.entityHit.isImmuneToFire()) + { +- boolean flag = result.entityHit.attackEntityFrom(DamageSource.causeFireballDamage(this, this.shootingEntity), 5.0F); +- +- if (flag) +- { ++ // CraftBukkit start - Entity damage by entity event + combust event ++ isIncendiary = result.entityHit.attackEntityFrom(DamageSource.causeFireballDamage(this, this.shootingEntity), 5.0F); ++ if (isIncendiary) { + this.applyEnchantments(this.shootingEntity, result.entityHit); +- result.entityHit.setFire(5); ++ // result.entityHit.setFire(5); ++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), result.entityHit.getBukkitEntity(), 5); ++ result.entityHit.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ result.entityHit.setFire(event.getDuration()); ++ } + } + } + } +@@ -60,13 +69,15 @@ + flag1 = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.shootingEntity); + } + +- if (flag1) ++ if (isIncendiary) + { + BlockPos blockpos = result.getBlockPos().offset(result.sideHit); + + if (this.world.isAirBlock(blockpos)) + { +- this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockpos.getX(), blockpos.getY(), blockpos.getZ(), this).isCancelled()) { ++ this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ } + } + } + } diff --git a/patches/net/minecraft/entity/projectile/EntitySpectralArrow.java.patch b/patches/net/minecraft/entity/projectile/EntitySpectralArrow.java.patch new file mode 100644 index 00000000..38afbb5c --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntitySpectralArrow.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntitySpectralArrow.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntitySpectralArrow.java +@@ -12,7 +12,7 @@ + + public class EntitySpectralArrow extends EntityArrow + { +- private int duration = 200; ++ public int duration = 200; + + public EntitySpectralArrow(World worldIn) + { diff --git a/patches/net/minecraft/entity/projectile/EntityThrowable.java.patch b/patches/net/minecraft/entity/projectile/EntityThrowable.java.patch new file mode 100644 index 00000000..bbffb0f0 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityThrowable.java.patch @@ -0,0 +1,40 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityThrowable.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityThrowable.java +@@ -31,8 +31,8 @@ + private Block inTile; + protected boolean inGround; + public int throwableShake; +- protected EntityLivingBase thrower; +- private String throwerName; ++ public EntityLivingBase thrower; ++ public String throwerName; + private int ticksInGround; + private int ticksInAir; + public Entity ignoreEntity; +@@ -57,6 +57,7 @@ + { + this(worldIn, throwerIn.posX, throwerIn.posY + (double)throwerIn.getEyeHeight() - 0.10000000149011612D, throwerIn.posZ); + this.thrower = throwerIn; ++ this.projectileSource = (org.bukkit.entity.LivingEntity) throwerIn.getBukkitEntity(); // CraftBukkit + } + + protected void entityInit() +@@ -196,7 +197,7 @@ + { + flag = true; + } +- else if (this.thrower != null && this.ticksExisted < 2 && this.ignoreEntity == null) ++ else if (this.thrower != null && this.ticksExisted < 2 && this.ignoreEntity == null && this.thrower == entity1) // CraftBukkit - MC-88491 + { + this.ignoreEntity = entity1; + flag = true; +@@ -247,6 +248,9 @@ + else if (!net.minecraftforge.event.ForgeEventFactory.onProjectileImpact(this, raytraceresult)) + { + this.onImpact(raytraceresult); ++ if (this.isDead) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, raytraceresult); ++ } + } + } + diff --git a/patches/net/minecraft/entity/projectile/EntityTippedArrow.java.patch b/patches/net/minecraft/entity/projectile/EntityTippedArrow.java.patch new file mode 100644 index 00000000..9c439541 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityTippedArrow.java.patch @@ -0,0 +1,43 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityTippedArrow.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityTippedArrow.java +@@ -26,7 +26,7 @@ + { + private static final DataParameter COLOR = EntityDataManager.createKey(EntityTippedArrow.class, DataSerializers.VARINT); + private PotionType potion = PotionTypes.EMPTY; +- private final Set customPotionEffects = Sets.newHashSet(); ++ public final Set customPotionEffects = Sets.newHashSet(); //CraftBukkit + private boolean fixedColor; + + public EntityTippedArrow(World worldIn) +@@ -146,12 +146,30 @@ + } + } + ++ // CraftBukkit start accessor methods ++ public void refreshEffects() { ++ this.getDataManager().set(EntityTippedArrow.COLOR, PotionUtils.getPotionColorFromEffectList(PotionUtils.mergeEffects(this.potion, this.customPotionEffects))); ++ } ++ ++ public String getType() { ++ return PotionType.REGISTRY.getNameForObject(this.potion).toString(); ++ } ++ ++ public void setType(String string) { ++ this.potion = PotionType.REGISTRY.getObject(new ResourceLocation(string)); ++ this.dataManager.set(EntityTippedArrow.COLOR, PotionUtils.getPotionColorFromEffectList(PotionUtils.mergeEffects(this.potion, this.customPotionEffects))); ++ } ++ ++ public boolean isTipped() { ++ return !(this.customPotionEffects.isEmpty() && this.potion == PotionTypes.EMPTY); ++ } ++ + public int getColor() + { + return ((Integer)this.dataManager.get(COLOR)).intValue(); + } + +- private void setFixedColor(int p_191507_1_) ++ public void setFixedColor(int p_191507_1_) + { + this.fixedColor = true; + this.dataManager.set(COLOR, Integer.valueOf(p_191507_1_)); diff --git a/patches/net/minecraft/entity/projectile/EntityWitherSkull.java.patch b/patches/net/minecraft/entity/projectile/EntityWitherSkull.java.patch new file mode 100644 index 00000000..91585da4 --- /dev/null +++ b/patches/net/minecraft/entity/projectile/EntityWitherSkull.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/entity/projectile/EntityWitherSkull.java ++++ ../src-work/minecraft/net/minecraft/entity/projectile/EntityWitherSkull.java +@@ -3,7 +3,6 @@ + import net.minecraft.block.Block; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.EntityLivingBase; +-import net.minecraft.entity.boss.EntityWither; + import net.minecraft.init.MobEffects; + import net.minecraft.network.datasync.DataParameter; + import net.minecraft.network.datasync.DataSerializers; +@@ -18,6 +17,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.ExplosionPrimeEvent; + + public class EntityWitherSkull extends EntityFireball + { +@@ -78,7 +78,7 @@ + { + if (this.shootingEntity != null) + { +- if (result.entityHit.attackEntityFrom(DamageSource.causeMobDamage(this.shootingEntity), 8.0F)) ++ if (result.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.shootingEntity), 8.0F)) + { + if (result.entityHit.isEntityAlive()) + { +@@ -86,7 +86,7 @@ + } + else + { +- this.shootingEntity.heal(5.0F); ++ this.shootingEntity.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); + } + } + } +@@ -115,7 +115,13 @@ + } + } + +- this.world.newExplosion(this, this.posX, this.posY, this.posZ, 1.0F, false, net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.shootingEntity)); ++ // this.world.newExplosion(this, this.posX, this.posY, this.posZ, 1.0F, false, net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.shootingEntity)); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.world.newExplosion(this, this.posX, this.posY, this.posZ, event.getRadius(), event.getFire(), net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.shootingEntity)); ++ } + this.setDead(); + } + } diff --git a/patches/net/minecraft/init/Bootstrap.java.patch b/patches/net/minecraft/init/Bootstrap.java.patch new file mode 100644 index 00000000..91980702 --- /dev/null +++ b/patches/net/minecraft/init/Bootstrap.java.patch @@ -0,0 +1,562 @@ +--- ../src-base/minecraft/net/minecraft/init/Bootstrap.java ++++ ../src-work/minecraft/net/minecraft/init/Bootstrap.java +@@ -3,17 +3,11 @@ + import com.mojang.authlib.GameProfile; + import java.io.File; + import java.io.PrintStream; ++import java.util.List; + import java.util.Random; + import java.util.UUID; + import net.minecraft.advancements.AdvancementManager; +-import net.minecraft.block.Block; +-import net.minecraft.block.BlockDispenser; +-import net.minecraft.block.BlockFire; +-import net.minecraft.block.BlockLiquid; +-import net.minecraft.block.BlockPumpkin; +-import net.minecraft.block.BlockShulkerBox; +-import net.minecraft.block.BlockSkull; +-import net.minecraft.block.BlockTNT; ++import net.minecraft.block.*; + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.dispenser.BehaviorDefaultDispenseItem; +@@ -71,6 +65,12 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++import org.bukkit.event.world.StructureGrowEvent; + + public class Bootstrap + { +@@ -193,15 +193,42 @@ + double d0 = source.getX() + (double)enumfacing.getFrontOffsetX(); + double d1 = (double)((float)(source.getBlockPos().getY() + enumfacing.getFrontOffsetY()) + 0.2F); + double d2 = source.getZ() + (double)enumfacing.getFrontOffsetZ(); +- Entity entity = ItemMonsterPlacer.spawnCreature(source.getWorld(), ItemMonsterPlacer.getNamedIdFrom(stack), d0, d1, d2); ++// Entity entity = ItemMonsterPlacer.spawnCreature(source.getWorld(), ItemMonsterPlacer.getNamedIdFrom(stack), d0, d1, d2); ++ // CraftBukkit start ++ World world = source.getWorld(); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); + ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1, d2)); ++ world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); ++ ++ Entity entity = ItemMonsterPlacer.spawnCreature(source.getWorld(), ItemMonsterPlacer.getNamedIdFrom(stack), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG); + if (entity instanceof EntityLivingBase && stack.hasDisplayName()) + { + entity.setCustomNameTag(stack.getDisplayName()); + } ++ // CraftBukkit end + + ItemMonsterPlacer.applyItemEntityDataToEntity(source.getWorld(), (EntityPlayer)null, stack, entity); +- stack.shrink(1); ++// stack.shrink(1); // Handled during event processing + return stack; + } + }); +@@ -213,7 +240,33 @@ + double d0 = source.getX() + (double)enumfacing.getFrontOffsetX(); + double d1 = (double)((float)source.getBlockPos().getY() + 0.2F); + double d2 = source.getZ() + (double)enumfacing.getFrontOffsetZ(); +- EntityFireworkRocket entityfireworkrocket = new EntityFireworkRocket(source.getWorld(), d0, d1, d2, stack); ++// EntityFireworkRocket entityfireworkrocket = new EntityFireworkRocket(source.getWorld(), d0, d1, d2, stack); ++ World world = source.getWorld(); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1, d2)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); ++ EntityFireworkRocket entityfireworkrocket = new EntityFireworkRocket(source.getWorld(), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), itemstack1); + source.getWorld().spawnEntity(entityfireworkrocket); + stack.shrink(1); + return stack; +@@ -237,8 +290,34 @@ + double d3 = random.nextGaussian() * 0.05D + (double)enumfacing.getFrontOffsetX(); + double d4 = random.nextGaussian() * 0.05D + (double)enumfacing.getFrontOffsetY(); + double d5 = random.nextGaussian() * 0.05D + (double)enumfacing.getFrontOffsetZ(); +- world.spawnEntity(new EntitySmallFireball(world, d0, d1, d2, d3, d4, d5)); +- stack.shrink(1); ++// world.spawnEntity(new EntitySmallFireball(world, d0, d1, d2, d3, d4, d5)); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ EntitySmallFireball fireball = new EntitySmallFireball(world, d0, d1, d2, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); ++ fireball.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource((TileEntityDispenser) source.getBlockTileEntity()); ++ ++// stack.shrink(1); + return stack; + } + protected void playDispenseSound(IBlockSource source) +@@ -246,20 +325,63 @@ + source.getWorld().playEvent(1018, source.getBlockPos(), 0); + } + }); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.OAK)); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.SPRUCE_BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.SPRUCE)); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.BIRCH_BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.BIRCH)); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.JUNGLE_BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.JUNGLE)); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.DARK_OAK_BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.DARK_OAK)); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.ACACIA_BOAT, new Bootstrap.BehaviorDispenseBoat(EntityBoat.Type.ACACIA)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.BOAT, new BehaviorDispenseBoat(EntityBoat.Type.OAK)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.SPRUCE_BOAT, new BehaviorDispenseBoat(EntityBoat.Type.SPRUCE)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.BIRCH_BOAT, new BehaviorDispenseBoat(EntityBoat.Type.BIRCH)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.JUNGLE_BOAT, new BehaviorDispenseBoat(EntityBoat.Type.JUNGLE)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.DARK_OAK_BOAT, new BehaviorDispenseBoat(EntityBoat.Type.DARK_OAK)); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.ACACIA_BOAT, new BehaviorDispenseBoat(EntityBoat.Type.ACACIA)); + IBehaviorDispenseItem ibehaviordispenseitem = new BehaviorDefaultDispenseItem() + { + private final BehaviorDefaultDispenseItem dispenseBehavior = new BehaviorDefaultDispenseItem(); + public ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { +- ItemBucket itembucket = (ItemBucket)stack.getItem(); +- BlockPos blockpos = source.getBlockPos().offset((EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING)); +- return itembucket.tryPlaceContainedLiquid((EntityPlayer)null, source.getWorld(), blockpos) ? new ItemStack(Items.BUCKET) : this.dispenseBehavior.dispense(source, stack); ++ ItemBucket itembucket = (ItemBucket) stack.getItem(); ++ BlockPos blockpos = source.getBlockPos().offset(source.getBlockState().getValue(BlockDispenser.FACING)); ++// return itembucket.tryPlaceContainedLiquid((EntityPlayer)null, source.getWorld(), blockpos) ? new ItemStack(Items.BUCKET) : this.dispenseBehavior.dispense(source, stack); ++ World world = source.getWorld(); ++ int x = blockpos.getX(); ++ int y = blockpos.getY(); ++ int z = blockpos.getZ(); ++ if (world.isAirBlock(blockpos) || !world.getBlockState(blockpos).getMaterial().isSolid()) { ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ itembucket = (ItemBucket) CraftItemStack.asNMSCopy(event.getItem()).getItem(); ++ } ++ ++ if (itembucket.tryPlaceContainedLiquid(null, source.getWorld(), blockpos)) { ++ // CraftBukkit start - Handle stacked buckets ++ Item item = Items.BUCKET; ++ stack.shrink(1); ++ if (stack.isEmpty()) { ++ stack.setItem(Items.BUCKET); ++ stack.setCount(1); ++ } else if (((TileEntityDispenser) source.getBlockTileEntity()).addItemStack(new ItemStack(item)) < 0) { ++ this.dispenseBehavior.dispense(source, new ItemStack(item)); ++ } ++ // CraftBukkit end ++ return stack; ++ } else { ++ return this.dispenseBehavior.dispense(source, stack); ++ } + } + }; + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.LAVA_BUCKET, ibehaviordispenseitem); +@@ -293,6 +415,26 @@ + item = Items.LAVA_BUCKET; + } + ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockpos.getX(), blockpos.getY(), blockpos.getZ())); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ + world.setBlockToAir(blockpos); + stack.shrink(1); + +@@ -311,21 +453,42 @@ + } + } + }); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.FLINT_AND_STEEL, new Bootstrap.BehaviorDispenseOptional() ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.FLINT_AND_STEEL, new BehaviorDispenseOptional() + { + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { + World world = source.getWorld(); ++ ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ + this.successful = true; +- BlockPos blockpos = source.getBlockPos().offset((EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING)); ++ BlockPos blockpos = source.getBlockPos().offset(source.getBlockState().getValue(BlockDispenser.FACING)); + + if (world.isAirBlock(blockpos)) + { +- world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); +- +- if (stack.attemptDamageItem(1, world.rand, (EntityPlayerMP)null)) +- { +- stack.setCount(0); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockpos.getX(), blockpos.getY(), blockpos.getZ(), source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()).isCancelled()) { ++ world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); ++ if (stack.attemptDamageItem(1, world.rand, null)) { ++ stack.setCount(0); ++ } + } + } + else if (world.getBlockState(blockpos).getBlock() == Blocks.TNT) +@@ -341,7 +504,7 @@ + return stack; + } + }); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.DYE, new Bootstrap.BehaviorDispenseOptional() ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.DYE, new BehaviorDispenseOptional() + { + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { +@@ -351,7 +514,28 @@ + { + World world = source.getWorld(); + BlockPos blockpos = source.getBlockPos().offset((EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING)); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); + ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ world.captureTreeGeneration = true; ++ + if (ItemDye.applyBonemeal(stack, world, blockpos)) + { + if (!world.isRemote) +@@ -364,6 +548,28 @@ + this.successful = false; + } + ++ world.captureTreeGeneration = false; ++ if (world.capturedBlockSnapshots.size() > 0) { ++ TreeType treeType = BlockSapling.treeType; ++ BlockSapling.treeType = null; ++ Location location = new Location(world.getWorld(), blockpos.getX(), blockpos.getY(), blockpos.getZ()); ++ List blocks = (List) world.capturedBlockSnapshots.clone(); ++ List blockstates = new java.util.ArrayList(); ++ for (net.minecraftforge.common.util.BlockSnapshot snapshot : blocks) { ++ blockstates.add(new org.bukkit.craftbukkit.block.CraftBlockState(snapshot)); ++ } ++ world.capturedBlockSnapshots.clear(); ++ StructureGrowEvent structureEvent = null; ++ if (treeType != null) { ++ structureEvent = new StructureGrowEvent(location, treeType, false, null, blockstates); ++ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); ++ } ++ if (structureEvent == null || !structureEvent.isCancelled()) { ++ for (org.bukkit.block.BlockState blockstate : blockstates) { ++ blockstate.update(true); ++ } ++ } ++ } + return stack; + } + else +@@ -378,14 +584,38 @@ + { + World world = source.getWorld(); + BlockPos blockpos = source.getBlockPos().offset((EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING)); +- EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double)blockpos.getX() + 0.5D, (double)blockpos.getY(), (double)blockpos.getZ() + 0.5D, (EntityLivingBase)null); ++// EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double)blockpos.getX() + 0.5D, (double)blockpos.getY(), (double)blockpos.getZ() + 0.5D, (EntityLivingBase)null); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), null); + world.spawnEntity(entitytntprimed); + world.playSound((EntityPlayer)null, entitytntprimed.posX, entitytntprimed.posY, entitytntprimed.posZ, SoundEvents.ENTITY_TNT_PRIMED, SoundCategory.BLOCKS, 1.0F, 1.0F); +- stack.shrink(1); ++// stack.shrink(1); // CraftBukkit - handled above + return stack; + } + }); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.SKULL, new Bootstrap.BehaviorDispenseOptional() ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Items.SKULL, new BehaviorDispenseOptional() + { + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { +@@ -393,6 +623,27 @@ + EnumFacing enumfacing = (EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING); + BlockPos blockpos = source.getBlockPos().offset(enumfacing); + BlockSkull blockskull = Blocks.SKULL; ++ ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockpos.getX(), blockpos.getY(), blockpos.getZ())); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ + this.successful = true; + + if (world.isAirBlock(blockpos) && blockskull.canDispenserPlace(world, blockpos, stack)) +@@ -449,13 +700,34 @@ + return stack; + } + }); +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Item.getItemFromBlock(Blocks.PUMPKIN), new Bootstrap.BehaviorDispenseOptional() ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Item.getItemFromBlock(Blocks.PUMPKIN), new BehaviorDispenseOptional() + { + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack) + { + World world = source.getWorld(); + BlockPos blockpos = source.getBlockPos().offset((EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING)); + BlockPumpkin blockpumpkin = (BlockPumpkin)Blocks.PUMPKIN; ++ ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockpos.getX(), blockpos.getY(), blockpos.getZ())); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ + this.successful = true; + + if (world.isAirBlock(blockpos) && blockpumpkin.canDispenserPlace(world, blockpos)) +@@ -483,7 +755,7 @@ + + for (EnumDyeColor enumdyecolor : EnumDyeColor.values()) + { +- BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Item.getItemFromBlock(BlockShulkerBox.getBlockByColor(enumdyecolor)), new Bootstrap.BehaviorDispenseShulkerBox()); ++ BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(Item.getItemFromBlock(BlockShulkerBox.getBlockByColor(enumdyecolor)), new BehaviorDispenseShulkerBox()); + } + } + +@@ -588,11 +860,37 @@ + d3 = 0.0D; + } + +- EntityBoat entityboat = new EntityBoat(world, d0, d1 + d3, d2); ++// EntityBoat entityboat = new EntityBoat(world, d0, d1 + d3, d2); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ EntityBoat entityboat = new EntityBoat(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); + entityboat.setBoatType(this.boatType); + entityboat.rotationYaw = enumfacing.getHorizontalAngle(); +- world.spawnEntity(entityboat); +- stack.shrink(1); ++ if (!world.spawnEntity(entityboat)) ++ stack.grow(1); ++// world.spawnEntity(entityboat); ++// stack.shrink(1); // CraftBukkit - handled during event processing + return stack; + } + +@@ -612,7 +910,7 @@ + } + } + +- static class BehaviorDispenseShulkerBox extends Bootstrap.BehaviorDispenseOptional ++ static class BehaviorDispenseShulkerBox extends BehaviorDispenseOptional + { + private BehaviorDispenseShulkerBox() + { +@@ -624,6 +922,27 @@ + World world = source.getWorld(); + EnumFacing enumfacing = (EnumFacing)source.getBlockState().getValue(BlockDispenser.FACING); + BlockPos blockpos = source.getBlockPos().offset(enumfacing); ++ ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockpos.getX(), blockpos.getY(), blockpos.getZ())); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ + this.successful = world.mayPlace(block, blockpos, false, EnumFacing.DOWN, (Entity)null); + + if (this.successful) diff --git a/patches/net/minecraft/inventory/Container.java.patch b/patches/net/minecraft/inventory/Container.java.patch new file mode 100644 index 00000000..375bac0f --- /dev/null +++ b/patches/net/minecraft/inventory/Container.java.patch @@ -0,0 +1,225 @@ +--- ../src-base/minecraft/net/minecraft/inventory/Container.java ++++ ../src-work/minecraft/net/minecraft/inventory/Container.java +@@ -18,7 +18,26 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.Event; ++import org.bukkit.event.inventory.InventoryDragEvent; ++import org.bukkit.event.inventory.InventoryType; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.InventoryView; + ++import javax.annotation.Nullable; ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ + public abstract class Container + { + public NonNullList inventoryItemStacks = NonNullList.create(); +@@ -31,7 +50,77 @@ + private final Set dragSlots = Sets.newHashSet(); + protected List listeners = Lists.newArrayList(); + private final Set playerList = Sets.newHashSet(); ++ private int tickCount; // Spigot + ++ public boolean checkReachable = true; ++ private InventoryView bukkitView; ++ private boolean isBukkitViewCreated; ++ ++ @Nullable ++ public InventoryView getBukkitView() { ++ if(!isBukkitViewCreated) { ++ isBukkitViewCreated = true; ++ bukkitView = computeBukkitView(); ++ return bukkitView; ++ } ++ return bukkitView; ++ } ++ ++ public void transferTo(Container other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) { ++ InventoryView source = this.getBukkitView(), destination = other.getBukkitView(); ++ if (source != null) { ++ try { ++ ((CraftInventory) source.getTopInventory()).getInventory().onClose(player); ++ } catch (AbstractMethodError ex) { ++ } ++ try { ++ ((CraftInventory) source.getBottomInventory()).getInventory().onClose(player); ++ } catch (AbstractMethodError ex) { ++ } ++ } ++ if (destination != null) { ++ try { ++ ((CraftInventory) destination.getTopInventory()).getInventory().onOpen(player); ++ } catch (AbstractMethodError ex) { ++ } ++ try { ++ ((CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player); ++ } catch (AbstractMethodError ex) { ++ } ++ } ++ } ++ ++ public void setBukkitView(InventoryView bukkitView) { ++ this.bukkitView = bukkitView; ++ isBukkitViewCreated = true; ++ } ++ ++ @Nullable ++ private InventoryView computeBukkitView() { ++ Set uniqueInventorySet = new HashSet<>(); ++ for(Slot slot : inventorySlots) ++ if (slot.inventory != null) uniqueInventorySet.add(slot.inventory); ++ List inventories = new ArrayList<>(uniqueInventorySet); ++ InventoryPlayer playerInv = null; ++ for(Iterator it = inventories.iterator(); it.hasNext();) { ++ IInventory inv = it.next(); ++ if(inv instanceof InventoryPlayer) { ++ playerInv = (InventoryPlayer) inv; ++ it.remove(); ++ break; ++ } ++ } ++ if(playerInv == null) ++ return null; ++ CraftPlayer bukkitPlayer = (CraftPlayer) playerInv.player.getBukkitEntity(); ++ Inventory craftInv; ++ if(inventories.size() != 1) ++ craftInv = Bukkit.getServer().createInventory(bukkitPlayer, InventoryType.CHEST); ++ else ++ craftInv = new CraftInventory(inventories.get(0)); ++ return new CraftInventoryView(bukkitPlayer, craftInv, this); ++ } ++ + protected Slot addSlotToContainer(Slot slotIn) + { + slotIn.slotNumber = this.inventorySlots.size(); +@@ -79,7 +168,7 @@ + ItemStack itemstack = ((Slot)this.inventorySlots.get(i)).getStack(); + ItemStack itemstack1 = this.inventoryItemStacks.get(i); + +- if (!ItemStack.areItemStacksEqual(itemstack1, itemstack)) ++ if (!ItemStack.fastMatches(itemstack1, itemstack) || (tickCount % org.spigotmc.SpigotConfig.itemDirtyTicks == 0 && !ItemStack.areItemStacksEqual(itemstack1, itemstack))) // Spigot + { + boolean clientStackChanged = !ItemStack.areItemStacksEqualUsingNBTShareTag(itemstack1, itemstack); + itemstack1 = itemstack.isEmpty() ? ItemStack.EMPTY : itemstack.copy(); +@@ -92,6 +181,7 @@ + } + } + } ++ tickCount++; // Spigot + } + + public boolean enchantItem(EntityPlayer playerIn, int id) +@@ -174,7 +264,7 @@ + { + ItemStack itemstack9 = inventoryplayer.getItemStack().copy(); + int k1 = inventoryplayer.getItemStack().getCount(); +- ++ Map draggedSlots = new HashMap<>(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack) + for (Slot slot8 : this.dragSlots) + { + ItemStack itemstack13 = inventoryplayer.getItemStack(); +@@ -192,12 +282,50 @@ + } + + k1 -= itemstack14.getCount() - j3; +- slot8.putStack(itemstack14); ++ // slot8.putStack(itemstack14); ++ draggedSlots.put(slot8.slotNumber, itemstack14); // CraftBukkit - Put in map instead of setting + } + } + +- itemstack9.setCount(k1); +- inventoryplayer.setItemStack(itemstack9); ++ // itemstack9.setCount(k1); ++ // inventoryplayer.setItemStack(itemstack9); ++ // CraftBukkit start - InventoryDragEvent ++ InventoryView view = getBukkitView(); ++ org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack9); ++ newcursor.setAmount(k1); ++ Map eventmap = new HashMap(); ++ for (Map.Entry ditem : draggedSlots.entrySet()) { ++ eventmap.put(ditem.getKey(), CraftItemStack.asBukkitCopy(ditem.getValue())); ++ } ++ ++ // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory. ++ ItemStack oldCursor = inventoryplayer.getItemStack(); ++ inventoryplayer.setItemStack(CraftItemStack.asNMSCopy(newcursor)); ++ ++ InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.dragMode == 1, eventmap); ++ player.world.getServer().getPluginManager().callEvent(event); ++ ++ // Whether or not a change was made to the inventory that requires an update. ++ boolean needsUpdate = event.getResult() != Event.Result.DEFAULT; ++ ++ if (event.getResult() != Event.Result.DENY) { ++ for (Map.Entry dslot : draggedSlots.entrySet()) { ++ view.setItem(dslot.getKey(), CraftItemStack.asBukkitCopy(dslot.getValue())); ++ } ++ // The only time the carried item will be set to null is if the inventory is closed by the server. ++ // If the inventory is closed by the server, then the cursor items are dropped. This is why we change the cursor early. ++ if (inventoryplayer.getItemStack() != null) { ++ inventoryplayer.setItemStack(CraftItemStack.asNMSCopy(event.getCursor())); ++ needsUpdate = true; ++ } ++ } else { ++ inventoryplayer.setItemStack(oldCursor); ++ } ++ ++ if (needsUpdate && player instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) player).sendContainerToPlayer(this); ++ } ++ // CraftBukkit end + } + + this.resetDrag(); +@@ -219,8 +347,9 @@ + { + if (dragType == 0) + { +- player.dropItem(inventoryplayer.getItemStack(), true); ++ ItemStack carried = inventoryplayer.getItemStack(); + inventoryplayer.setItemStack(ItemStack.EMPTY); ++ player.dropItem(carried, true); + } + + if (dragType == 1) +@@ -348,6 +477,15 @@ + } + + slot6.onSlotChanged(); ++ // CraftBukkit start - Make sure the client has the right slot contents ++ if (player instanceof EntityPlayerMP && slot6.getSlotStackLimit() != 64) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketSetSlot(this.windowId, slot6.slotNumber, slot6.getStack())); ++ // Updating a crafting inventory makes the client reset the result slot, have to send it again ++ if (getBukkitView() != null && (this.getBukkitView().getType() == InventoryType.WORKBENCH || this.getBukkitView().getType() == InventoryType.CRAFTING)) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketSetSlot(this.windowId, 0, this.getSlot(0).getStack())); ++ } ++ } ++ // CraftBukkit end + } + } + } +@@ -797,7 +935,9 @@ + p_192389_4_.setRecipeUsed(irecipe); + itemstack = irecipe.getCraftingResult(p_192389_3_); + } +- ++ if (p_192389_3_.resultInventory != null && getBukkitView() != null) { ++ itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(p_192389_3_, itemstack, getBukkitView(), false); // CraftBukkit ++ } + p_192389_4_.setInventorySlotContents(0, itemstack); + entityplayermp.connection.sendPacket(new SPacketSetSlot(this.windowId, 0, itemstack)); + } diff --git a/patches/net/minecraft/inventory/ContainerBeacon.java.patch b/patches/net/minecraft/inventory/ContainerBeacon.java.patch new file mode 100644 index 00000000..986a4f7e --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerBeacon.java.patch @@ -0,0 +1,57 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerBeacon.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerBeacon.java +@@ -1,21 +1,26 @@ + package net.minecraft.inventory; + + import net.minecraft.entity.player.EntityPlayer; +-import net.minecraft.init.Items; +-import net.minecraft.item.Item; ++import net.minecraft.entity.player.InventoryPlayer; + import net.minecraft.item.ItemStack; ++import net.minecraft.tileentity.TileEntityBeacon; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; + + public class ContainerBeacon extends Container + { + private final IInventory tileBeacon; +- private final ContainerBeacon.BeaconSlot beaconSlot; ++ private final BeaconSlot beaconSlot; + ++ private CraftInventoryView bukkitEntity = null; ++ private InventoryPlayer player; ++ + public ContainerBeacon(IInventory playerInventory, IInventory tileBeaconIn) + { ++ player = (InventoryPlayer) playerInventory; // CraftBukkit - TODO: check this + this.tileBeacon = tileBeaconIn; +- this.beaconSlot = new ContainerBeacon.BeaconSlot(tileBeaconIn, 0, 136, 110); ++ this.beaconSlot = new BeaconSlot(tileBeaconIn, 0, 136, 110); + this.addSlotToContainer(this.beaconSlot); + int i = 36; + int j = 137; +@@ -68,6 +73,7 @@ + + public boolean canInteractWith(EntityPlayer playerIn) + { ++ if (!this.checkReachable) return true; + return this.tileBeacon.isUsableByPlayer(playerIn); + } + +@@ -150,4 +156,15 @@ + return 1; + } + } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon((TileEntityBeacon) this.tileBeacon); // TODO - check this ++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this); ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerEnchantment.java.patch b/patches/net/minecraft/inventory/ContainerEnchantment.java.patch new file mode 100644 index 00000000..ad0fa1db --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerEnchantment.java.patch @@ -0,0 +1,236 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerEnchantment.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerEnchantment.java +@@ -1,6 +1,7 @@ + package net.minecraft.inventory; + + import java.util.List; ++import java.util.Map; + import java.util.Random; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.enchantment.Enchantment; +@@ -21,11 +22,19 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.enchantments.EnchantmentOffer; ++import org.bukkit.entity.Player; ++import org.bukkit.event.enchantment.EnchantItemEvent; ++import org.bukkit.event.enchantment.PrepareItemEnchantEvent; + + public class ContainerEnchantment extends Container + { + public IInventory tableInventory; +- private final World worldPointer; ++ private World worldPointer; + private final BlockPos position; + private final Random rand; + public int xpSeed; +@@ -33,6 +42,9 @@ + public int[] enchantClue; + public int[] worldClue; + ++ private CraftInventoryView bukkitEntity = null; ++ private Player player; ++ + @SideOnly(Side.CLIENT) + public ContainerEnchantment(InventoryPlayer playerInv, World worldIn) + { +@@ -52,6 +64,11 @@ + super.markDirty(); + ContainerEnchantment.this.onCraftMatrixChanged(this); + } ++ ++ @Override ++ public Location getLocation() { ++ return new Location(worldIn.getWorld(), position.getX(), position.getY(), position.getZ()); ++ } + }; + this.rand = new Random(); + this.enchantLevels = new int[3]; +@@ -73,7 +90,7 @@ + }); + this.addSlotToContainer(new Slot(this.tableInventory, 1, 35, 47) + { +- java.util.List ores = net.minecraftforge.oredict.OreDictionary.getOres("gemLapis"); ++ List ores = net.minecraftforge.oredict.OreDictionary.getOres("gemLapis"); + public boolean isItemValid(ItemStack stack) + { + for (ItemStack ore : ores) +@@ -94,6 +111,8 @@ + { + this.addSlotToContainer(new Slot(playerInv, k, 8 + k * 18, 142)); + } ++ ++ player = (Player) playerInv.player.getBukkitEntity(); + } + + protected void broadcastData(IContainerListener crafting) +@@ -158,7 +177,7 @@ + { + ItemStack itemstack = inventoryIn.getStackInSlot(0); + +- if (!itemstack.isEmpty() && itemstack.isItemEnchantable()) ++ if (!itemstack.isEmpty()/* && itemstack.isItemEnchantable()*/) // CraftBukkit - relax condition + { + if (!this.worldPointer.isRemote) + { +@@ -214,6 +233,41 @@ + } + } + ++ // CraftBukkit start ++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack); ++ EnchantmentOffer[] offers = new EnchantmentOffer[3]; ++ for (int j = 0; j < 3; ++j) { ++ org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? org.bukkit.enchantments.Enchantment.getById(this.enchantClue[j]) : null; ++ offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.worldClue[j], this.enchantLevels[j]) : null; ++ } ++ ++ PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, this.getBukkitView(), this.worldPointer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, offers, (int) power); ++ event.setCancelled(!itemstack.isItemEnchantable()); ++ this.worldPointer.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ for (int j = 0; j < 3; ++j) { ++ this.enchantLevels[j] = 0; ++ this.enchantClue[j] = -1; ++ this.worldClue[j] = -1; ++ } ++ return; ++ } ++ ++ for (int j = 0; j < 3; j++) { ++ EnchantmentOffer offer = event.getOffers()[j]; ++ if (offer != null) { ++ this.enchantLevels[j] = offer.getCost(); ++ this.enchantClue[j] = offer.getEnchantment().getId(); ++ this.worldClue[j] = offer.getEnchantmentLevel(); ++ } else { ++ this.enchantLevels[j] = 0; ++ this.enchantClue[j] = -1; ++ this.worldClue[j] = -1; ++ } ++ } ++ // CraftBukkit end ++ + this.detectAndSendChanges(); + } + } +@@ -245,31 +299,63 @@ + { + List list = this.getEnchantmentList(itemstack, id, this.enchantLevels[id]); + +- if (!list.isEmpty()) ++ if (true || !list.isEmpty()) + { +- playerIn.onEnchant(itemstack, i); ++// playerIn.onEnchant(itemstack, i); // Moved down + boolean flag = itemstack.getItem() == Items.BOOK; + ++ Map enchants = new java.util.HashMap(); ++ for (Object obj : list) { ++ EnchantmentData instance = (EnchantmentData) obj; ++ enchants.put(org.bukkit.enchantments.Enchantment.getById(Enchantment.getEnchantmentID(instance.enchantment)), instance.enchantmentLevel); ++ } ++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack); ++ ++ EnchantItemEvent event = new EnchantItemEvent((Player) playerIn.getBukkitEntity(), this.getBukkitView(), this.worldPointer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, this.enchantLevels[id], enchants, id); ++ this.worldPointer.getServer().getPluginManager().callEvent(event); ++ ++ int level = event.getExpLevelCost(); ++ if (event.isCancelled() || (level > playerIn.experienceLevel && !playerIn.capabilities.isCreativeMode) || event.getEnchantsToAdd().isEmpty()) { ++ return false; ++ } ++ + if (flag) + { + itemstack = new ItemStack(Items.ENCHANTED_BOOK); + this.tableInventory.setInventorySlotContents(0, itemstack); + } + +- for (int j = 0; j < list.size(); ++j) +- { +- EnchantmentData enchantmentdata = list.get(j); +- +- if (flag) +- { +- ItemEnchantedBook.addEnchantment(itemstack, enchantmentdata); ++// for (int j = 0; j < list.size(); ++j) ++// { ++// EnchantmentData enchantmentdata = list.get(j); ++ for (Map.Entry entry : event.getEnchantsToAdd().entrySet()) { ++ try { ++ if (flag) { ++ int enchantId = entry.getKey().getId(); ++ if (Enchantment.getEnchantmentByID(enchantId) == null) { ++ continue; ++ } ++// if (flag) ++// { ++// ItemEnchantedBook.addEnchantment(itemstack, enchantmentdata); ++// } ++// else ++// { ++// itemstack.addEnchantment(enchantmentdata.enchantment, enchantmentdata.enchantmentLevel); ++ EnchantmentData weightedrandomenchant = new EnchantmentData(Enchantment.getEnchantmentByID(enchantId), entry.getValue()); ++ ItemEnchantedBook.addEnchantment(itemstack, weightedrandomenchant); ++ } else { ++ item.addUnsafeEnchantment(entry.getKey(), entry.getValue()); ++ } ++ } catch (IllegalArgumentException e) { ++ /* Just swallow invalid enchantments */ + } +- else +- { +- itemstack.addEnchantment(enchantmentdata.enchantment, enchantmentdata.enchantmentLevel); +- } + } + ++ playerIn.onEnchant(itemstack, i); ++ ++ ++ // CraftBukkit - TODO: let plugins change this + if (!playerIn.capabilities.isCreativeMode) + { + itemstack1.shrink(i); +@@ -325,7 +411,11 @@ + public void onContainerClosed(EntityPlayer playerIn) + { + super.onContainerClosed(playerIn); +- ++ // CraftBukkit Start - If an enchantable was opened from a null location, set the world to the player's world, preventing a crash ++ if (this.worldPointer == null) { ++ this.worldPointer = playerIn.getEntityWorld(); ++ } ++ // CraftBukkit end + if (!this.worldPointer.isRemote) + { + this.clearContainer(playerIn, playerIn.world, this.tableInventory); +@@ -334,6 +424,7 @@ + + public boolean canInteractWith(EntityPlayer playerIn) + { ++ if (!this.checkReachable) return true; + if (this.worldPointer.getBlockState(this.position).getBlock() != Blocks.ENCHANTING_TABLE) + { + return false; +@@ -412,4 +503,15 @@ + + return itemstack; + } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.tableInventory); ++ bukkitEntity = new CraftInventoryView(this.player, inventory, this); ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerHorseChest.java.patch b/patches/net/minecraft/inventory/ContainerHorseChest.java.patch new file mode 100644 index 00000000..4b8a68e5 --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerHorseChest.java.patch @@ -0,0 +1,34 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerHorseChest.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerHorseChest.java +@@ -1,14 +1,19 @@ + package net.minecraft.inventory; + ++import net.minecraft.entity.passive.AbstractHorse; ++import net.minecraft.entity.passive.EntityAnimal; + import net.minecraft.util.text.ITextComponent; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + + public class ContainerHorseChest extends InventoryBasic + { +- public ContainerHorseChest(String inventoryTitle, int slotCount) ++ private EntityAnimal entityAnimal; ++ ++ public ContainerHorseChest(String inventoryTitle, int slotCount, AbstractHorse owner) + { +- super(inventoryTitle, false, slotCount); ++ super(inventoryTitle, false, slotCount, (org.bukkit.entity.AbstractHorse) owner.getBukkitEntity()); ++ this.entityAnimal = owner; + } + + @SideOnly(Side.CLIENT) +@@ -16,4 +21,9 @@ + { + super(inventoryTitle, slotCount); + } ++ ++ public EntityAnimal getAnimal() ++ { ++ return this.entityAnimal; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerHorseInventory.java.patch b/patches/net/minecraft/inventory/ContainerHorseInventory.java.patch new file mode 100644 index 00000000..db21dc6d --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerHorseInventory.java.patch @@ -0,0 +1,43 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerHorseInventory.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerHorseInventory.java +@@ -3,18 +3,25 @@ + import net.minecraft.entity.passive.AbstractChestHorse; + import net.minecraft.entity.passive.AbstractHorse; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.InventoryPlayer; + import net.minecraft.init.Items; + import net.minecraft.item.ItemStack; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.inventory.InventoryView; + + public class ContainerHorseInventory extends Container + { + private final IInventory horseInventory; + private final AbstractHorse horse; + ++ org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity; ++ InventoryPlayer player; ++ + public ContainerHorseInventory(IInventory playerInventory, IInventory horseInventoryIn, final AbstractHorse horse, EntityPlayer player) + { ++ this.player = (InventoryPlayer) playerInventory; + this.horseInventory = horseInventoryIn; + this.horse = horse; + int i = 3; +@@ -133,4 +140,14 @@ + super.onContainerClosed(playerIn); + this.horseInventory.closeInventory(playerIn); + } ++ ++ @Override ++ public InventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ return bukkitEntity = new CraftInventoryView(player.player.getBukkitEntity(), horseInventory.getOwner().getInventory(), this); ++ } ++ + } diff --git a/patches/net/minecraft/inventory/ContainerMerchant.java.patch b/patches/net/minecraft/inventory/ContainerMerchant.java.patch new file mode 100644 index 00000000..3c783105 --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerMerchant.java.patch @@ -0,0 +1,41 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerMerchant.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerMerchant.java +@@ -5,6 +5,7 @@ + import net.minecraft.entity.player.InventoryPlayer; + import net.minecraft.item.ItemStack; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; + + public class ContainerMerchant extends Container + { +@@ -12,6 +13,9 @@ + private final InventoryMerchant merchantInventory; + private final World world; + ++ private CraftInventoryView bukkitEntity = null; ++ private InventoryPlayer player; ++ + public ContainerMerchant(InventoryPlayer playerInventory, IMerchant merchant, World worldIn) + { + this.merchant = merchant; +@@ -20,6 +24,7 @@ + this.addSlotToContainer(new Slot(this.merchantInventory, 0, 36, 53)); + this.addSlotToContainer(new Slot(this.merchantInventory, 1, 62, 53)); + this.addSlotToContainer(new SlotMerchantResult(playerInventory.player, merchant, this.merchantInventory, 2, 120, 53)); ++ this.player = playerInventory; + + for (int i = 0; i < 3; ++i) + { +@@ -137,4 +142,12 @@ + } + } + } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity == null) { ++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(merchantInventory), this); ++ } ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerPlayer.java.patch b/patches/net/minecraft/inventory/ContainerPlayer.java.patch new file mode 100644 index 00000000..b9834a77 --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerPlayer.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerPlayer.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerPlayer.java +@@ -9,19 +9,30 @@ + import net.minecraft.item.ItemStack; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; + + public class ContainerPlayer extends Container + { + private static final EntityEquipmentSlot[] VALID_EQUIPMENT_SLOTS = new EntityEquipmentSlot[] {EntityEquipmentSlot.HEAD, EntityEquipmentSlot.CHEST, EntityEquipmentSlot.LEGS, EntityEquipmentSlot.FEET}; +- public InventoryCrafting craftMatrix = new InventoryCrafting(this, 2, 2); +- public InventoryCraftResult craftResult = new InventoryCraftResult(); ++ public InventoryCrafting craftMatrix; // CraftBukkit - move initialization into constructor ++ public InventoryCraftResult craftResult; // CraftBukkit - move initialization into constructor + public boolean isLocalWorld; + private final EntityPlayer player; + ++ private CraftInventoryView bukkitEntity = null; ++ private InventoryPlayer playerInventory; ++ + public ContainerPlayer(InventoryPlayer playerInventory, boolean localWorld, EntityPlayer playerIn) + { + this.isLocalWorld = localWorld; + this.player = playerIn; ++ // CraftBukkit start ++ this.craftResult = new InventoryCraftResult(); // CraftBukkit - moved to before InventoryCrafting construction ++ this.craftMatrix = new InventoryCrafting(this, 2, 2, playerInventory.player); // CraftBukkit - pass player ++ this.craftMatrix.resultInventory = this.craftResult; // CraftBukkit - let InventoryCrafting know about its result slot ++ this.playerInventory = playerInventory; // CraftBukkit - save player ++ // CraftBukkit end + this.addSlotToContainer(new SlotCrafting(playerInventory.player, this.craftMatrix, this.craftResult, 0, 154, 28)); + + for (int i = 0; i < 2; ++i) +@@ -202,4 +213,15 @@ + { + return slotIn.inventory != this.craftResult && super.canMergeSlot(stack, slotIn); + } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftMatrix, this.craftResult); ++ bukkitEntity = new CraftInventoryView(this.playerInventory.player.getBukkitEntity(), inventory, this); ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerRepair.java.patch b/patches/net/minecraft/inventory/ContainerRepair.java.patch new file mode 100644 index 00000000..55718000 --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerRepair.java.patch @@ -0,0 +1,102 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerRepair.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerRepair.java +@@ -18,6 +18,7 @@ + import org.apache.commons.lang3.StringUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; + + public class ContainerRepair extends Container + { +@@ -28,9 +29,13 @@ + private final BlockPos selfPosition; + public int maximumCost; + public int materialCost; +- private String repairedItemName; ++ public String repairedItemName; + private final EntityPlayer player; + ++ private int lastLevelCost; ++ private CraftInventoryView bukkitEntity; ++ private InventoryPlayer playerInventory; ++ + @SideOnly(Side.CLIENT) + public ContainerRepair(InventoryPlayer playerInventory, World worldIn, EntityPlayer player) + { +@@ -39,6 +44,7 @@ + + public ContainerRepair(InventoryPlayer playerInventory, final World worldIn, final BlockPos blockPosIn, EntityPlayer player) + { ++ this.playerInventory = playerInventory; + this.outputSlot = new InventoryCraftResult(); + this.inputSlots = new InventoryBasic("Repair", true, 2) + { +@@ -178,7 +184,8 @@ + + if (l2 <= 0) + { +- this.outputSlot.setInventorySlotContents(0, ItemStack.EMPTY); ++// this.outputSlot.setInventorySlotContents(0, ItemStack.EMPTY); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); + this.maximumCost = 0; + return; + } +@@ -299,7 +306,8 @@ + + if (flag3 && !flag2) + { +- this.outputSlot.setInventorySlotContents(0, ItemStack.EMPTY); ++// this.outputSlot.setInventorySlotContents(0, ItemStack.EMPTY); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); + this.maximumCost = 0; + return; + } +@@ -358,7 +366,8 @@ + EnchantmentHelper.setEnchantments(map, itemstack1); + } + +- this.outputSlot.setInventorySlotContents(0, itemstack1); ++// this.outputSlot.setInventorySlotContents(0, itemstack1); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), itemstack1); + this.detectAndSendChanges(); + } + } +@@ -390,6 +399,7 @@ + + public boolean canInteractWith(EntityPlayer playerIn) + { ++ if (!this.checkReachable) return true; + if (this.world.getBlockState(this.selfPosition).getBlock() != Blocks.ANVIL) + { + return false; +@@ -471,4 +481,30 @@ + + this.updateRepairOutput(); + } ++ ++ @Override ++ public void detectAndSendChanges() { ++ super.detectAndSendChanges(); ++ for (int i = 0; i < this.listeners.size(); ++i) { ++ IContainerListener icrafting = this.listeners.get(i); ++ ++ if (this.lastLevelCost != this.maximumCost) { ++ icrafting.sendWindowProperty(this, 0, this.maximumCost); ++ } ++ } ++ ++ this.lastLevelCost = this.maximumCost; ++ } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryAnvil( ++ new org.bukkit.Location(world.getWorld(), selfPosition.getX(), selfPosition.getY(), selfPosition.getZ()), this.inputSlots, this.outputSlot, this); ++ bukkitEntity = new CraftInventoryView(this.playerInventory.player.getBukkitEntity(), inventory, this); ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/ContainerWorkbench.java.patch b/patches/net/minecraft/inventory/ContainerWorkbench.java.patch new file mode 100644 index 00000000..f6d8bcf9 --- /dev/null +++ b/patches/net/minecraft/inventory/ContainerWorkbench.java.patch @@ -0,0 +1,57 @@ +--- ../src-base/minecraft/net/minecraft/inventory/ContainerWorkbench.java ++++ ../src-work/minecraft/net/minecraft/inventory/ContainerWorkbench.java +@@ -6,17 +6,28 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; + + public class ContainerWorkbench extends Container + { +- public InventoryCrafting craftMatrix = new InventoryCrafting(this, 3, 3); +- public InventoryCraftResult craftResult = new InventoryCraftResult(); ++ public InventoryCrafting craftMatrix; // CraftBukkit - move initialization into constructor ++ public InventoryCraftResult craftResult; // CraftBukkit - move initialization into constructor + private final World world; + private final BlockPos pos; + private final EntityPlayer player; + ++ private CraftInventoryView bukkitEntity = null; ++ private InventoryPlayer playerInventory; ++ + public ContainerWorkbench(InventoryPlayer playerInventory, World worldIn, BlockPos posIn) + { ++ // CraftBukkit start - Switched order of IInventory construction and stored player ++ this.craftResult = new InventoryCraftResult(); ++ this.craftMatrix = new InventoryCrafting(this, 3, 3, playerInventory.player); // CraftBukkit - pass player ++ this.craftMatrix.resultInventory = this.craftResult; ++ this.playerInventory = playerInventory; ++ // CraftBukkit end + this.world = worldIn; + this.pos = posIn; + this.player = playerInventory.player; +@@ -61,6 +72,7 @@ + + public boolean canInteractWith(EntityPlayer playerIn) + { ++ if (!this.checkReachable) return true; + if (this.world.getBlockState(this.pos).getBlock() != Blocks.CRAFTING_TABLE) + { + return false; +@@ -140,4 +152,15 @@ + { + return slotIn.inventory != this.craftResult && super.canMergeSlot(stack, slotIn); + } ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (bukkitEntity != null) { ++ return bukkitEntity; ++ } ++ ++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftMatrix, this.craftResult); ++ bukkitEntity = new CraftInventoryView(this.playerInventory.player.getBukkitEntity(), inventory, this); ++ return bukkitEntity; ++ } + } diff --git a/patches/net/minecraft/inventory/IInventory.java.patch b/patches/net/minecraft/inventory/IInventory.java.patch new file mode 100644 index 00000000..0d39d465 --- /dev/null +++ b/patches/net/minecraft/inventory/IInventory.java.patch @@ -0,0 +1,35 @@ +--- ../src-base/minecraft/net/minecraft/inventory/IInventory.java ++++ ../src-work/minecraft/net/minecraft/inventory/IInventory.java +@@ -3,9 +3,9 @@ + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.item.ItemStack; + import net.minecraft.world.IWorldNameable; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; + +-public interface IInventory extends IWorldNameable +-{ ++public interface IInventory extends IWorldNameable { + int getSizeInventory(); + + boolean isEmpty(); +@@ -37,4 +37,20 @@ + int getFieldCount(); + + void clear(); ++ ++ java.util.List getContents(); ++ ++ void onOpen(CraftHumanEntity who); ++ ++ void onClose(CraftHumanEntity who); ++ ++ java.util.List getViewers(); ++ ++ org.bukkit.inventory.InventoryHolder getOwner(); ++ ++ void setMaxStackSize(int size); ++ ++ org.bukkit.Location getLocation(); ++ ++ int MAX_STACK = 64; + } diff --git a/patches/net/minecraft/inventory/InventoryBasic.java.patch b/patches/net/minecraft/inventory/InventoryBasic.java.patch new file mode 100644 index 00000000..eae80957 --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryBasic.java.patch @@ -0,0 +1,59 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryBasic.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryBasic.java +@@ -10,6 +10,9 @@ + import net.minecraft.util.text.TextComponentTranslation; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + + public class InventoryBasic implements IInventory + { +@@ -19,8 +22,46 @@ + private List changeListeners; + private boolean hasCustomName; + ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ protected org.bukkit.inventory.InventoryHolder bukkitOwner; ++ ++ public List getContents() { ++ return this.inventoryContents; ++ } ++ ++ public void setMaxStackSize(int i) { ++ maxStack = i; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return bukkitOwner; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return null; ++ } ++ + public InventoryBasic(String title, boolean customName, int slotCount) + { ++ this(title, customName, slotCount, null); ++ } ++ ++ public InventoryBasic(String title, boolean customName, int slotCount, org.bukkit.inventory.InventoryHolder owner) { ++ this.bukkitOwner = owner; + this.inventoryTitle = title; + this.hasCustomName = customName; + this.slotsCount = slotCount; diff --git a/patches/net/minecraft/inventory/InventoryCraftResult.java.patch b/patches/net/minecraft/inventory/InventoryCraftResult.java.patch new file mode 100644 index 00000000..100ec64e --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryCraftResult.java.patch @@ -0,0 +1,65 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryCraftResult.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryCraftResult.java +@@ -1,5 +1,6 @@ + package net.minecraft.inventory; + ++import java.util.List; + import javax.annotation.Nullable; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.item.ItemStack; +@@ -8,12 +9,46 @@ + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentString; + import net.minecraft.util.text.TextComponentTranslation; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + + public class InventoryCraftResult implements IInventory + { + private final NonNullList stackResult = NonNullList.withSize(1, ItemStack.EMPTY); + private IRecipe recipeUsed; + ++ private int maxStack = MAX_STACK; ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ public java.util.List getContents() { ++ return this.stackResult; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return null; // Result slots don't get an owner ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return null; ++ } ++ + public int getSizeInventory() + { + return 1; +@@ -69,7 +104,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public void markDirty() diff --git a/patches/net/minecraft/inventory/InventoryCrafting.java.patch b/patches/net/minecraft/inventory/InventoryCrafting.java.patch new file mode 100644 index 00000000..ad3aec62 --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryCrafting.java.patch @@ -0,0 +1,77 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryCrafting.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryCrafting.java +@@ -3,18 +3,73 @@ + import net.minecraft.client.util.RecipeItemHelper; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.item.ItemStack; ++import net.minecraft.item.crafting.IRecipe; + import net.minecraft.util.NonNullList; + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentString; + import net.minecraft.util.text.TextComponentTranslation; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.inventory.InventoryType; + ++import java.util.List; ++ + public class InventoryCrafting implements IInventory + { + private final NonNullList stackList; + private final int inventoryWidth; + private final int inventoryHeight; +- private final Container eventHandler; ++ public final Container eventHandler; + ++ // CraftBukkit start - add fields ++ public IRecipe currentRecipe; ++ public IInventory resultInventory; ++ private EntityPlayer owner; ++ private int maxStack = MAX_STACK; ++ ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public List getContents() { ++ return this.stackList; ++ } ++ ++ public InventoryType getInvType() { ++ return stackList.size() == 4 ? InventoryType.CRAFTING : InventoryType.WORKBENCH; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return (owner == null) ? null : owner.getBukkitEntity(); ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ resultInventory.setMaxStackSize(size); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return owner.getBukkitEntity().getLocation(); ++ } ++ ++ public InventoryCrafting(Container container, int i, int j, EntityPlayer player) { ++ this(container, i, j); ++ this.owner = player; ++ } ++ // CraftBukkit end ++ + public InventoryCrafting(Container eventHandlerIn, int width, int height) + { + this.stackList = NonNullList.withSize(width * height, ItemStack.EMPTY); diff --git a/patches/net/minecraft/inventory/InventoryEnderChest.java.patch b/patches/net/minecraft/inventory/InventoryEnderChest.java.patch new file mode 100644 index 00000000..c337e19c --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryEnderChest.java.patch @@ -0,0 +1,38 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryEnderChest.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryEnderChest.java +@@ -5,14 +5,19 @@ + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.tileentity.TileEntityEnderChest; ++import org.bukkit.Location; ++import org.bukkit.inventory.InventoryHolder; + + public class InventoryEnderChest extends InventoryBasic + { + private TileEntityEnderChest associatedChest; + +- public InventoryEnderChest() ++ private final EntityPlayer owner; ++ ++ public InventoryEnderChest(EntityPlayer owner) + { + super("container.enderchest", false, 27); ++ this.owner = owner; + } + + public void setChestTileEntity(TileEntityEnderChest chestTileEntity) +@@ -84,4 +89,14 @@ + super.closeInventory(player); + this.associatedChest = null; + } ++ ++ public InventoryHolder getBukkitOwner() { ++ return owner.getBukkitEntity(); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return new Location(this.associatedChest.getWorld().getWorld(), this.associatedChest.getPos().getX(), this.associatedChest.getPos().getY(), this.associatedChest.getPos().getZ()); ++ } ++ + } diff --git a/patches/net/minecraft/inventory/InventoryLargeChest.java.patch b/patches/net/minecraft/inventory/InventoryLargeChest.java.patch new file mode 100644 index 00000000..c60a3350 --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryLargeChest.java.patch @@ -0,0 +1,74 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryLargeChest.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryLargeChest.java +@@ -8,13 +8,59 @@ + import net.minecraft.util.text.TextComponentTranslation; + import net.minecraft.world.ILockableContainer; + import net.minecraft.world.LockCode; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + ++import java.util.ArrayList; ++import java.util.List; ++ + public class InventoryLargeChest implements ILockableContainer + { + private final String name; +- private final ILockableContainer upperChest; +- private final ILockableContainer lowerChest; ++ public final ILockableContainer upperChest; ++ public final ILockableContainer lowerChest; + ++ public List transaction = new ArrayList(); ++ ++ public List getContents() { ++ List result = new ArrayList(this.getSizeInventory()); ++ for (int i = 0; i < this.getSizeInventory(); i++) { ++ result.add(this.getStackInSlot(i)); ++ } ++ return result; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.upperChest.onOpen(who); ++ this.lowerChest.onOpen(who); ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.upperChest.onClose(who); ++ this.lowerChest.onClose(who); ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return null; // This method won't be called since CraftInventoryDoubleChest doesn't defer to here ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.upperChest.setMaxStackSize(size); ++ this.lowerChest.setMaxStackSize(size); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return upperChest.getLocation(); // TODO: right? ++ } ++ + public InventoryLargeChest(String nameIn, ILockableContainer upperChestIn, ILockableContainer lowerChestIn) + { + this.name = nameIn; +@@ -108,7 +154,8 @@ + + public int getInventoryStackLimit() + { +- return this.upperChest.getInventoryStackLimit(); ++ // return this.upperChest.getInventoryStackLimit(); ++ return Math.min(this.upperChest.getInventoryStackLimit(), this.lowerChest.getInventoryStackLimit()); // CraftBukkit - check both sides + } + + public void markDirty() diff --git a/patches/net/minecraft/inventory/InventoryMerchant.java.patch b/patches/net/minecraft/inventory/InventoryMerchant.java.patch new file mode 100644 index 00000000..f9ff82ba --- /dev/null +++ b/patches/net/minecraft/inventory/InventoryMerchant.java.patch @@ -0,0 +1,84 @@ +--- ../src-base/minecraft/net/minecraft/inventory/InventoryMerchant.java ++++ ../src-work/minecraft/net/minecraft/inventory/InventoryMerchant.java +@@ -1,6 +1,7 @@ + package net.minecraft.inventory; + + import net.minecraft.entity.IMerchant; ++import net.minecraft.entity.passive.EntityVillager; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.item.ItemStack; + import net.minecraft.util.NonNullList; +@@ -9,15 +10,54 @@ + import net.minecraft.util.text.TextComponentTranslation; + import net.minecraft.village.MerchantRecipe; + import net.minecraft.village.MerchantRecipeList; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.entity.CraftVillager; + ++import java.util.List; ++import org.bukkit.entity.HumanEntity; ++ + public class InventoryMerchant implements IInventory + { + private final IMerchant merchant; + private final NonNullList slots = NonNullList.withSize(3, ItemStack.EMPTY); + private final EntityPlayer player; + private MerchantRecipe currentRecipe; +- private int currentRecipeIndex; ++ public int currentRecipeIndex; + ++ private int maxStack = MAX_STACK; ++ ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public List getContents() { ++ return this.slots; ++ } ++ ++ public void setMaxStackSize(int i) { ++ maxStack = i; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return (merchant instanceof EntityVillager) ? (CraftVillager) ((EntityVillager) this.merchant).getBukkitEntity() : null; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return (merchant instanceof EntityVillager) ? ((EntityVillager) this.merchant).getBukkitEntity().getLocation() : null; ++ } ++ + public InventoryMerchant(EntityPlayer thePlayerIn, IMerchant theMerchantIn) + { + this.player = thePlayerIn; +@@ -110,7 +150,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public boolean isUsableByPlayer(EntityPlayer player) +@@ -218,4 +258,8 @@ + { + this.slots.clear(); + } ++ ++ public EntityPlayer getPlayer() { ++ return player; ++ } + } diff --git a/patches/net/minecraft/inventory/SlotFurnaceOutput.java.patch b/patches/net/minecraft/inventory/SlotFurnaceOutput.java.patch new file mode 100644 index 00000000..22b17b86 --- /dev/null +++ b/patches/net/minecraft/inventory/SlotFurnaceOutput.java.patch @@ -0,0 +1,30 @@ +--- ../src-base/minecraft/net/minecraft/inventory/SlotFurnaceOutput.java ++++ ../src-work/minecraft/net/minecraft/inventory/SlotFurnaceOutput.java +@@ -4,7 +4,10 @@ + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.item.ItemStack; + import net.minecraft.item.crafting.FurnaceRecipes; ++import net.minecraft.tileentity.TileEntityFurnace; + import net.minecraft.util.math.MathHelper; ++import org.bukkit.entity.Player; ++import org.bukkit.event.inventory.FurnaceExtractEvent; + + public class SlotFurnaceOutput extends Slot + { +@@ -70,6 +73,16 @@ + i = j; + } + ++ Player player = (Player) this.player.getBukkitEntity(); ++ TileEntityFurnace furnace = ((TileEntityFurnace) this.inventory); ++ org.bukkit.block.Block block = this.player.world.getWorld().getBlockAt(furnace.getPos().getX(), furnace.getPos().getY(), furnace.getPos().getZ()); ++ ++ if (removeCount != 0) { ++ FurnaceExtractEvent event = new FurnaceExtractEvent(player, block, org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(stack.getItem()), removeCount, i); ++ this.player.world.getServer().getPluginManager().callEvent(event); ++ i = event.getExpToDrop(); ++ } ++ + while (i > 0) + { + int k = EntityXPOrb.getXPSplit(i); diff --git a/patches/net/minecraft/item/Item.java.patch b/patches/net/minecraft/item/Item.java.patch new file mode 100644 index 00000000..e3718fba --- /dev/null +++ b/patches/net/minecraft/item/Item.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/item/Item.java ++++ ../src-work/minecraft/net/minecraft/item/Item.java +@@ -635,7 +635,7 @@ + */ + public int getEntityLifespan(ItemStack itemStack, World world) + { +- return 6000; ++ return world.spigotConfig.itemDespawnRate; + } + + /** diff --git a/patches/net/minecraft/item/ItemArmor.java.patch b/patches/net/minecraft/item/ItemArmor.java.patch new file mode 100644 index 00000000..045aaff6 --- /dev/null +++ b/patches/net/minecraft/item/ItemArmor.java.patch @@ -0,0 +1,40 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemArmor.java ++++ ../src-work/minecraft/net/minecraft/item/ItemArmor.java +@@ -29,6 +29,8 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; + + public class ItemArmor extends Item + { +@@ -63,6 +65,28 @@ + EntityLivingBase entitylivingbase = list.get(0); + EntityEquipmentSlot entityequipmentslot = EntityLiving.getSlotForItemStack(stack); + ItemStack itemstack = stack.splitStack(1); ++ World world = blockSource.getWorld(); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(blockSource.getBlockPos().getX(), blockSource.getBlockPos().getY(), blockSource.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ itemstack.grow(1); ++ return itemstack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ itemstack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = (IBehaviorDispenseItem) BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != ItemArmor.DISPENSER_BEHAVIOR) { ++ idispensebehavior.dispense(blockSource, eventStack); ++ return itemstack; ++ } ++ } + entitylivingbase.setItemStackToSlot(entityequipmentslot, itemstack); + + if (entitylivingbase instanceof EntityLiving) diff --git a/patches/net/minecraft/item/ItemBlock.java.patch b/patches/net/minecraft/item/ItemBlock.java.patch new file mode 100644 index 00000000..cd0ac976 --- /dev/null +++ b/patches/net/minecraft/item/ItemBlock.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemBlock.java ++++ ../src-work/minecraft/net/minecraft/item/ItemBlock.java +@@ -19,7 +19,6 @@ + import net.minecraft.util.EnumFacing; + import net.minecraft.util.EnumHand; + import net.minecraft.util.NonNullList; +-import net.minecraft.util.SoundCategory; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; +@@ -55,7 +54,7 @@ + { + iblockstate1 = worldIn.getBlockState(pos); + SoundType soundtype = iblockstate1.getBlock().getSoundType(iblockstate1, worldIn, pos, player); +- worldIn.playSound(player, pos, soundtype.getPlaceSound(), SoundCategory.BLOCKS, (soundtype.getVolume() + 1.0F) / 2.0F, soundtype.getPitch() * 0.8F); ++ // worldIn.playSound(player, pos, soundtype.getPlaceSound(), SoundCategory.BLOCKS, (soundtype.getVolume() + 1.0F) / 2.0F, soundtype.getPitch() * 0.8F); // CraftBukkit - SPIGOT-1288 + itemstack.shrink(1); + } + diff --git a/patches/net/minecraft/item/ItemBoat.java.patch b/patches/net/minecraft/item/ItemBoat.java.patch new file mode 100644 index 00000000..64a1e6b4 --- /dev/null +++ b/patches/net/minecraft/item/ItemBoat.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemBoat.java ++++ ../src-work/minecraft/net/minecraft/item/ItemBoat.java +@@ -84,6 +84,11 @@ + } + else + { ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(playerIn, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, raytraceresult.getBlockPos(), raytraceresult.sideHit, itemstack, handIn); ++ ++ if (event.isCancelled()) { ++ return new ActionResult<>(EnumActionResult.PASS, itemstack); ++ } + Block block = worldIn.getBlockState(raytraceresult.getBlockPos()).getBlock(); + boolean flag1 = block == Blocks.WATER || block == Blocks.FLOWING_WATER; + EntityBoat entityboat = new EntityBoat(worldIn, raytraceresult.hitVec.x, flag1 ? raytraceresult.hitVec.y - 0.12D : raytraceresult.hitVec.y, raytraceresult.hitVec.z); +@@ -98,7 +103,9 @@ + { + if (!worldIn.isRemote) + { +- worldIn.spawnEntity(entityboat); ++ // worldIn.spawnEntity(entityboat); ++ if (!worldIn.spawnEntity(entityboat)) ++ return new ActionResult<>(EnumActionResult.PASS, itemstack); + } + + if (!playerIn.capabilities.isCreativeMode) diff --git a/patches/net/minecraft/item/ItemBow.java.patch b/patches/net/minecraft/item/ItemBow.java.patch new file mode 100644 index 00000000..de52efb8 --- /dev/null +++ b/patches/net/minecraft/item/ItemBow.java.patch @@ -0,0 +1,56 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemBow.java ++++ ../src-work/minecraft/net/minecraft/item/ItemBow.java +@@ -5,6 +5,7 @@ + import net.minecraft.enchantment.EnchantmentHelper; + import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.entity.projectile.EntityArrow; + import net.minecraft.init.Enchantments; + import net.minecraft.init.Items; +@@ -18,6 +19,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.EntityCombustEvent; + + public class ItemBow extends Item + { +@@ -135,8 +137,19 @@ + + if (EnchantmentHelper.getEnchantmentLevel(Enchantments.FLAME, stack) > 0) + { +- entityarrow.setFire(100); ++ // entityarrow.setFire(100); ++ EntityCombustEvent event = new EntityCombustEvent(entityarrow.getBukkitEntity(), 100); ++ entityarrow.world.getServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ entityarrow.setFire(event.getDuration()); ++ } + } ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityplayer, itemstack, entityarrow, f); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } + + stack.damageItem(1, entityplayer); + +@@ -145,7 +158,15 @@ + entityarrow.pickupStatus = EntityArrow.PickupStatus.CREATIVE_ONLY; + } + +- worldIn.spawnEntity(entityarrow); ++ // worldIn.spawnEntity(entityarrow); ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { ++ if (!worldIn.spawnEntity(entityarrow)) { ++ if (entityplayer instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) entityplayer).getBukkitEntity().updateInventory(); ++ } ++ return; ++ } ++ } + } + + worldIn.playSound((EntityPlayer)null, entityplayer.posX, entityplayer.posY, entityplayer.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.PLAYERS, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + f * 0.5F); diff --git a/patches/net/minecraft/item/ItemBucket.java.patch b/patches/net/minecraft/item/ItemBucket.java.patch new file mode 100644 index 00000000..b1c00ac9 --- /dev/null +++ b/patches/net/minecraft/item/ItemBucket.java.patch @@ -0,0 +1,108 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemBucket.java ++++ ../src-work/minecraft/net/minecraft/item/ItemBucket.java +@@ -23,6 +23,10 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.RayTraceResult; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.player.PlayerBucketEmptyEvent; ++import org.bukkit.event.player.PlayerBucketFillEvent; + + public class ItemBucket extends Item + { +@@ -72,17 +76,27 @@ + + if (material == Material.WATER && ((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue() == 0) + { ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(playerIn, blockpos.getX(), blockpos.getY(), blockpos.getZ(), null, itemstack, Items.WATER_BUCKET); ++ ++ if (event.isCancelled()) { ++ return new ActionResult<>(EnumActionResult.FAIL, itemstack); ++ } + worldIn.setBlockState(blockpos, Blocks.AIR.getDefaultState(), 11); + playerIn.addStat(StatList.getObjectUseStats(this)); + playerIn.playSound(SoundEvents.ITEM_BUCKET_FILL, 1.0F, 1.0F); +- return new ActionResult(EnumActionResult.SUCCESS, this.fillBucket(itemstack, playerIn, Items.WATER_BUCKET)); ++ return new ActionResult(EnumActionResult.SUCCESS, this.fillBucket(itemstack, playerIn, Items.WATER_BUCKET, event.getItemStack())); + } + else if (material == Material.LAVA && ((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue() == 0) + { ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent(playerIn, blockpos.getX(), blockpos.getY(), blockpos.getZ(), null, itemstack, Items.LAVA_BUCKET); ++ ++ if (event.isCancelled()) { ++ return new ActionResult<>(EnumActionResult.FAIL, itemstack); ++ } + playerIn.playSound(SoundEvents.ITEM_BUCKET_FILL_LAVA, 1.0F, 1.0F); + worldIn.setBlockState(blockpos, Blocks.AIR.getDefaultState(), 11); + playerIn.addStat(StatList.getObjectUseStats(this)); +- return new ActionResult(EnumActionResult.SUCCESS, this.fillBucket(itemstack, playerIn, Items.LAVA_BUCKET)); ++ return new ActionResult(EnumActionResult.SUCCESS, this.fillBucket(itemstack, playerIn, Items.LAVA_BUCKET, event.getItemStack())); + } + else + { +@@ -99,7 +113,7 @@ + { + return new ActionResult(EnumActionResult.FAIL, itemstack); + } +- else if (this.tryPlaceContainedLiquid(playerIn, worldIn, blockpos1)) ++ else if (this.tryPlaceContainedLiquid(playerIn, worldIn, blockpos1, raytraceresult.sideHit, blockpos, itemstack)) + { + if (playerIn instanceof EntityPlayerMP) + { +@@ -143,8 +157,40 @@ + } + } + ++ // CraftBukkit - added ob.ItemStack result - TODO: Is this... the right way to handle this? ++ private ItemStack fillBucket(ItemStack emptyBuckets, EntityPlayer player, Item fullBucket, org.bukkit.inventory.ItemStack result) ++ { ++ if (player.capabilities.isCreativeMode) ++ { ++ return emptyBuckets; ++ } ++ else ++ { ++ emptyBuckets.shrink(1); ++ ++ if (emptyBuckets.isEmpty()) ++ { ++ // return new ItemStack(fullBucket); ++ return CraftItemStack.asNMSCopy(result); ++ } ++ else ++ { ++ if (!player.inventory.addItemStackToInventory(CraftItemStack.asNMSCopy(result))) ++ { ++ player.dropItem(CraftItemStack.asNMSCopy(result), false); ++ } ++ ++ return emptyBuckets; ++ } ++ } ++ } ++ + public boolean tryPlaceContainedLiquid(@Nullable EntityPlayer player, World worldIn, BlockPos posIn) + { ++ return tryPlaceContainedLiquid(player, worldIn, posIn, null, posIn, null); ++ } ++ ++ public boolean tryPlaceContainedLiquid(@Nullable EntityPlayer player, World worldIn, BlockPos posIn, EnumFacing enumdirection, BlockPos clicked, ItemStack itemstack) { + if (this.containedBlock == Blocks.AIR) + { + return false; +@@ -162,6 +208,14 @@ + } + else + { ++ if (player != null) { ++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent(player, clicked.getX(), clicked.getY(), clicked.getZ(), enumdirection, itemstack); ++ if (event.isCancelled()) { ++ // TODO: inventory not updated ++ return false; ++ } ++ } ++ + if (worldIn.provider.doesWaterVaporize() && this.containedBlock == Blocks.FLOWING_WATER) + { + int l = posIn.getX(); diff --git a/patches/net/minecraft/item/ItemChorusFruit.java.patch b/patches/net/minecraft/item/ItemChorusFruit.java.patch new file mode 100644 index 00000000..1e8f915d --- /dev/null +++ b/patches/net/minecraft/item/ItemChorusFruit.java.patch @@ -0,0 +1,36 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemChorusFruit.java ++++ ../src-work/minecraft/net/minecraft/item/ItemChorusFruit.java +@@ -2,10 +2,14 @@ + + import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.SoundEvents; + import net.minecraft.util.SoundCategory; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; ++import org.bukkit.Location; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerTeleportEvent; + + public class ItemChorusFruit extends ItemFood + { +@@ -30,6 +34,18 @@ + double d4 = MathHelper.clamp(entityLiving.posY + (double)(entityLiving.getRNG().nextInt(16) - 8), 0.0D, (double)(worldIn.getActualHeight() - 1)); + double d5 = entityLiving.posZ + (entityLiving.getRNG().nextDouble() - 0.5D) * 16.0D; + ++ if (entityLiving instanceof EntityPlayerMP) { ++ Player player = ((EntityPlayerMP) entityLiving).getBukkitEntity(); ++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), new Location(player.getWorld(), d3, d4, d5), PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT); ++ worldIn.getServer().getPluginManager().callEvent(teleEvent); ++ if (teleEvent.isCancelled()) { ++ break; ++ } ++ d3 = teleEvent.getTo().getX(); ++ d4 = teleEvent.getTo().getY(); ++ d5 = teleEvent.getTo().getZ(); ++ } ++ + if (entityLiving.isRiding()) + { + entityLiving.dismountRidingEntity(); diff --git a/patches/net/minecraft/item/ItemDye.java.patch b/patches/net/minecraft/item/ItemDye.java.patch new file mode 100644 index 00000000..c631d8d8 --- /dev/null +++ b/patches/net/minecraft/item/ItemDye.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemDye.java ++++ ../src-work/minecraft/net/minecraft/item/ItemDye.java +@@ -20,6 +20,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.entity.SheepDyeWoolEvent; + + public class ItemDye extends Item + { +@@ -175,6 +176,15 @@ + + if (!entitysheep.getSheared() && entitysheep.getFleeceColor() != enumdyecolor) + { ++ byte bColor = (byte) enumdyecolor.getMetadata(); ++ SheepDyeWoolEvent event = new SheepDyeWoolEvent((org.bukkit.entity.Sheep) entitysheep.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData(bColor)); ++ entitysheep.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ enumdyecolor = EnumDyeColor.byMetadata(event.getColor().getWoolData()); + entitysheep.setFleeceColor(enumdyecolor); + stack.shrink(1); + } diff --git a/patches/net/minecraft/item/ItemEnderPearl.java.patch b/patches/net/minecraft/item/ItemEnderPearl.java.patch new file mode 100644 index 00000000..10d13ab2 --- /dev/null +++ b/patches/net/minecraft/item/ItemEnderPearl.java.patch @@ -0,0 +1,45 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemEnderPearl.java ++++ ../src-work/minecraft/net/minecraft/item/ItemEnderPearl.java +@@ -3,6 +3,7 @@ + import net.minecraft.creativetab.CreativeTabs; + import net.minecraft.entity.item.EntityEnderPearl; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.SoundEvents; + import net.minecraft.stats.StatList; + import net.minecraft.util.ActionResult; +@@ -23,6 +24,19 @@ + { + ItemStack itemstack = playerIn.getHeldItem(handIn); + ++ // CraftBukkit start - change order ++ if (!worldIn.isRemote) { ++ EntityEnderPearl entityenderpearl = new EntityEnderPearl(worldIn, playerIn); ++ ++ entityenderpearl.shoot(playerIn, playerIn.rotationPitch, playerIn.rotationYaw, 0.0F, 1.5F, 1.0F); ++ if (!worldIn.spawnEntity(entityenderpearl)) { ++ if (playerIn instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) playerIn).getBukkitEntity().updateInventory(); ++ } ++ return new ActionResult<>(EnumActionResult.FAIL, itemstack); ++ } ++ } ++ + if (!playerIn.capabilities.isCreativeMode) + { + itemstack.shrink(1); +@@ -31,12 +45,14 @@ + worldIn.playSound((EntityPlayer)null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_ENDERPEARL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + playerIn.getCooldownTracker().setCooldown(this, 20); + ++ /* + if (!worldIn.isRemote) + { + EntityEnderPearl entityenderpearl = new EntityEnderPearl(worldIn, playerIn); + entityenderpearl.shoot(playerIn, playerIn.rotationPitch, playerIn.rotationYaw, 0.0F, 1.5F, 1.0F); + worldIn.spawnEntity(entityenderpearl); + } ++ */ + + playerIn.addStat(StatList.getObjectUseStats(this)); + return new ActionResult(EnumActionResult.SUCCESS, itemstack); diff --git a/patches/net/minecraft/item/ItemFireball.java.patch b/patches/net/minecraft/item/ItemFireball.java.patch new file mode 100644 index 00000000..74cc9e7c --- /dev/null +++ b/patches/net/minecraft/item/ItemFireball.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemFireball.java ++++ ../src-work/minecraft/net/minecraft/item/ItemFireball.java +@@ -38,6 +38,12 @@ + { + if (worldIn.getBlockState(pos).getMaterial() == Material.AIR) + { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, player).isCancelled()) { ++ if (!player.capabilities.isCreativeMode) { ++ itemstack.shrink(1); ++ } ++ return EnumActionResult.PASS; ++ } + worldIn.playSound((EntityPlayer)null, pos, SoundEvents.ITEM_FIRECHARGE_USE, SoundCategory.BLOCKS, 1.0F, (itemRand.nextFloat() - itemRand.nextFloat()) * 0.2F + 1.0F); + worldIn.setBlockState(pos, Blocks.FIRE.getDefaultState()); + } diff --git a/patches/net/minecraft/item/ItemFishingRod.java.patch b/patches/net/minecraft/item/ItemFishingRod.java.patch new file mode 100644 index 00000000..c6c16ec1 --- /dev/null +++ b/patches/net/minecraft/item/ItemFishingRod.java.patch @@ -0,0 +1,35 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemFishingRod.java ++++ ../src-work/minecraft/net/minecraft/item/ItemFishingRod.java +@@ -16,6 +16,7 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.event.player.PlayerFishEvent; + + public class ItemFishingRod extends Item + { +@@ -74,7 +75,7 @@ + } + else + { +- worldIn.playSound((EntityPlayer)null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); ++ // worldIn.playSound((EntityPlayer)null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); // CraftBukkit - moved down + + if (!worldIn.isRemote) + { +@@ -93,6 +94,15 @@ + entityfishhook.setLuck(k); + } + ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((org.bukkit.entity.Player) playerIn.getBukkitEntity(), null, (org.bukkit.entity.Fish) entityfishhook.getBukkitEntity(), PlayerFishEvent.State.FISHING); ++ worldIn.getServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ playerIn.fishEntity = null; ++ return new ActionResult<>(EnumActionResult.PASS, itemstack); ++ } ++ worldIn.playSound(null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); ++ + worldIn.spawnEntity(entityfishhook); + } + diff --git a/patches/net/minecraft/item/ItemFlintAndSteel.java.patch b/patches/net/minecraft/item/ItemFlintAndSteel.java.patch new file mode 100644 index 00000000..8bccfa5c --- /dev/null +++ b/patches/net/minecraft/item/ItemFlintAndSteel.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemFlintAndSteel.java ++++ ../src-work/minecraft/net/minecraft/item/ItemFlintAndSteel.java +@@ -36,6 +36,12 @@ + { + if (worldIn.isAirBlock(pos)) + { ++ // CraftBukkit start - Store the clicked block ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(worldIn, pos.getX(), pos.getY(), pos.getZ(), org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, player).isCancelled()) { ++ itemstack.damageItem(1, player); ++ return EnumActionResult.PASS; ++ } ++ // CraftBukkit end + worldIn.playSound(player, pos, SoundEvents.ITEM_FLINTANDSTEEL_USE, SoundCategory.BLOCKS, 1.0F, itemRand.nextFloat() * 0.4F + 0.8F); + worldIn.setBlockState(pos, Blocks.FIRE.getDefaultState(), 11); + } diff --git a/patches/net/minecraft/item/ItemHangingEntity.java.patch b/patches/net/minecraft/item/ItemHangingEntity.java.patch new file mode 100644 index 00000000..ce47b70a --- /dev/null +++ b/patches/net/minecraft/item/ItemHangingEntity.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemHangingEntity.java ++++ ../src-work/minecraft/net/minecraft/item/ItemHangingEntity.java +@@ -11,6 +11,8 @@ + import net.minecraft.util.EnumHand; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.entity.Player; ++import org.bukkit.event.hanging.HangingPlaceEvent; + + public class ItemHangingEntity extends Item + { +@@ -35,6 +37,16 @@ + { + if (!worldIn.isRemote) + { ++ Player who = (player == null) ? null : (Player) player.getBukkitEntity(); ++ org.bukkit.block.Block blockClicked = worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(facing); ++ ++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityhanging.getBukkitEntity(), who, blockClicked, blockFace); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return EnumActionResult.FAIL; ++ } + entityhanging.playPlaceSound(); + worldIn.spawnEntity(entityhanging); + } diff --git a/patches/net/minecraft/item/ItemLead.java.patch b/patches/net/minecraft/item/ItemLead.java.patch new file mode 100644 index 00000000..e43c70e7 --- /dev/null +++ b/patches/net/minecraft/item/ItemLead.java.patch @@ -0,0 +1,30 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemLead.java ++++ ../src-work/minecraft/net/minecraft/item/ItemLead.java +@@ -12,6 +12,7 @@ + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.event.hanging.HangingPlaceEvent; + + public class ItemLead extends Item + { +@@ -55,8 +56,19 @@ + if (entityleashknot == null) + { + entityleashknot = EntityLeashKnot.createKnot(worldIn, fence); ++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityleashknot.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, worldIn.getWorld().getBlockAt(i, j, k), org.bukkit.block.BlockFace.SELF); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ entityleashknot.setDead(); ++ return false; ++ } + } + ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(entityliving, entityleashknot, player).isCancelled()) { ++ continue; ++ } ++ + entityliving.setLeashHolder(entityleashknot, true); + flag = true; + } diff --git a/patches/net/minecraft/item/ItemLilyPad.java.patch b/patches/net/minecraft/item/ItemLilyPad.java.patch new file mode 100644 index 00000000..f446d93f --- /dev/null +++ b/patches/net/minecraft/item/ItemLilyPad.java.patch @@ -0,0 +1,24 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemLilyPad.java ++++ ../src-work/minecraft/net/minecraft/item/ItemLilyPad.java +@@ -50,6 +50,8 @@ + + if (iblockstate.getMaterial() == Material.WATER && ((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue() == 0 && worldIn.isAirBlock(blockpos1)) + { ++ // CraftBukkit start - special case for handling block placement with water lilies ++ org.bukkit.block.BlockState blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(worldIn, blockpos1.getX(), blockpos1.getY(), blockpos1.getZ()); + // special case for handling block placement with water lilies + net.minecraftforge.common.util.BlockSnapshot blocksnapshot = net.minecraftforge.common.util.BlockSnapshot.getBlockSnapshot(worldIn, blockpos1); + worldIn.setBlockState(blockpos1, Blocks.WATERLILY.getDefaultState()); +@@ -61,6 +63,12 @@ + + worldIn.setBlockState(blockpos1, Blocks.WATERLILY.getDefaultState(), 11); + ++ org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(worldIn, playerIn, handIn, blockstate, blockpos.getX(), blockpos.getY(), blockpos.getZ()); ++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { ++ blockstate.update(true, false); ++ return new ActionResult<>(EnumActionResult.PASS, itemstack); ++ } ++ + if (playerIn instanceof EntityPlayerMP) + { + CriteriaTriggers.PLACED_BLOCK.trigger((EntityPlayerMP)playerIn, blockpos1, itemstack); diff --git a/patches/net/minecraft/item/ItemMap.java.patch b/patches/net/minecraft/item/ItemMap.java.patch new file mode 100644 index 00000000..a0528ca3 --- /dev/null +++ b/patches/net/minecraft/item/ItemMap.java.patch @@ -0,0 +1,114 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemMap.java ++++ ../src-work/minecraft/net/minecraft/item/ItemMap.java +@@ -21,11 +21,14 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.util.text.translation.I18n; + import net.minecraft.world.World; ++import net.minecraft.world.WorldServer; + import net.minecraft.world.biome.Biome; + import net.minecraft.world.chunk.Chunk; + import net.minecraft.world.storage.MapData; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Bukkit; ++import org.bukkit.event.server.MapInitializeEvent; + + public class ItemMap extends ItemMapBase + { +@@ -36,16 +39,20 @@ + + public static ItemStack setupNewMap(World worldIn, double worldX, double worldZ, byte scale, boolean trackingPosition, boolean unlimitedTracking) + { +- ItemStack itemstack = new ItemStack(Items.FILLED_MAP, 1, worldIn.getUniqueDataId("map")); ++ World worldMain = worldIn.getServer().getServer().worlds[0]; // CraftBukkit - store reference to primary world ++ // ItemStack itemstack = new ItemStack(Items.FILLED_MAP, 1, worldIn.getUniqueDataId("map")); ++ ItemStack itemstack = new ItemStack(Items.FILLED_MAP, 1, worldMain.getUniqueDataId("map")); // CraftBukkit - use primary world for maps + String s = "map_" + itemstack.getMetadata(); + MapData mapdata = new MapData(s); +- worldIn.setData(s, mapdata); ++ worldMain.setData(s, mapdata); + mapdata.scale = scale; + mapdata.calculateMapCenter(worldX, worldZ, mapdata.scale); +- mapdata.dimension = worldIn.provider.getDimension(); ++ // mapdata.dimension = worldIn.provider.getDimension(); ++ mapdata.dimension = ((WorldServer) worldIn).dimension; // CraftBukkit - use bukkit dimension + mapdata.trackingPosition = trackingPosition; + mapdata.unlimitedTracking = unlimitedTracking; + mapdata.markDirty(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callEvent(new MapInitializeEvent(mapdata.mapView)); + return itemstack; + } + +@@ -60,19 +67,25 @@ + @Nullable + public MapData getMapData(ItemStack stack, World worldIn) + { ++ World worldMain = worldIn.getServer().getServer().worlds[0]; // CraftBukkit - store reference to primary world + String s = "map_" + stack.getMetadata(); +- MapData mapdata = (MapData)worldIn.loadData(MapData.class, s); ++ // MapData mapdata = (MapData)worldIn.loadData(MapData.class, s); ++ MapData mapdata = (MapData) worldMain.loadData(MapData.class, s);// CraftBukkit - use primary world for maps + + if (mapdata == null && !worldIn.isRemote) + { +- stack.setItemDamage(worldIn.getUniqueDataId("map")); ++ stack.setItemDamage(worldMain.getUniqueDataId("map")); + s = "map_" + stack.getMetadata(); + mapdata = new MapData(s); + mapdata.scale = 3; + mapdata.calculateMapCenter((double)worldIn.getWorldInfo().getSpawnX(), (double)worldIn.getWorldInfo().getSpawnZ(), mapdata.scale); +- mapdata.dimension = worldIn.provider.getDimension(); ++ // mapdata.dimension = worldIn.provider.getDimension(); ++ mapdata.dimension = ((WorldServer) worldIn).dimension; // CraftBukkit - fixes Bukkit multiworld maps + mapdata.markDirty(); +- worldIn.setData(s, mapdata); ++ worldMain.setData(s, mapdata); ++ ++ MapInitializeEvent event = new MapInitializeEvent(mapdata.mapView); ++ Bukkit.getServer().getPluginManager().callEvent(event); + } + + return mapdata; +@@ -80,7 +93,8 @@ + + public void updateMapData(World worldIn, Entity viewer, MapData data) + { +- if (worldIn.provider.getDimension() == data.dimension && viewer instanceof EntityPlayer) ++ // CraftBukkit - world.provider -> ((WorldServer) world) ++ if (((WorldServer) worldIn).dimension == data.dimension && viewer instanceof EntityPlayer) + { + int i = 1 << data.scale; + int j = data.xCenter; +@@ -425,6 +439,7 @@ + protected static void scaleMap(ItemStack p_185063_0_, World p_185063_1_, int p_185063_2_) + { + MapData mapdata = Items.FILLED_MAP.getMapData(p_185063_0_, p_185063_1_); ++ p_185063_1_ = p_185063_1_.getServer().getServer().worlds[0]; + p_185063_0_.setItemDamage(p_185063_1_.getUniqueDataId("map")); + MapData mapdata1 = new MapData("map_" + p_185063_0_.getMetadata()); + +@@ -436,12 +451,15 @@ + mapdata1.dimension = mapdata.dimension; + mapdata1.markDirty(); + p_185063_1_.setData("map_" + p_185063_0_.getMetadata(), mapdata1); ++ MapInitializeEvent event = new MapInitializeEvent(mapdata1.mapView); ++ Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + protected static void enableMapTracking(ItemStack p_185064_0_, World p_185064_1_) + { + MapData mapdata = Items.FILLED_MAP.getMapData(p_185064_0_, p_185064_1_); ++ p_185064_1_ = p_185064_1_.getServer().getServer().worlds[0]; + p_185064_0_.setItemDamage(p_185064_1_.getUniqueDataId("map")); + MapData mapdata1 = new MapData("map_" + p_185064_0_.getMetadata()); + mapdata1.trackingPosition = true; +@@ -454,6 +472,8 @@ + mapdata1.dimension = mapdata.dimension; + mapdata1.markDirty(); + p_185064_1_.setData("map_" + p_185064_0_.getMetadata(), mapdata1); ++ MapInitializeEvent event = new MapInitializeEvent(mapdata1.mapView); ++ Bukkit.getServer().getPluginManager().callEvent(event); + } + } + diff --git a/patches/net/minecraft/item/ItemMinecart.java.patch b/patches/net/minecraft/item/ItemMinecart.java.patch new file mode 100644 index 00000000..d63282c0 --- /dev/null +++ b/patches/net/minecraft/item/ItemMinecart.java.patch @@ -0,0 +1,67 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemMinecart.java ++++ ../src-work/minecraft/net/minecraft/item/ItemMinecart.java +@@ -15,6 +15,8 @@ + import net.minecraft.util.EnumHand; + import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; + + public class ItemMinecart extends Item + { +@@ -64,15 +66,41 @@ + } + } + +- EntityMinecart entityminecart = EntityMinecart.create(world, d0, d1 + d3, d2, ((ItemMinecart)stack.getItem()).minecartType); ++ // EntityMinecart entityminecart = EntityMinecart.create(world, d0, d1 + d3, d2, ((ItemMinecart)stack.getItem()).minecartType); ++ ItemStack itemstack1 = stack.splitStack(1); ++ org.bukkit.block.Block block2 = world.getWorld().getBlockAt(source.getBlockPos().getX(), source.getBlockPos().getY(), source.getBlockPos().getZ()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); + ++ BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2)); ++ world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ stack.grow(1); ++ return stack; ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ stack.grow(1); ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ IBehaviorDispenseItem idispensebehavior = BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.getObject(eventStack.getItem()); ++ if (idispensebehavior != IBehaviorDispenseItem.DEFAULT_BEHAVIOR && idispensebehavior != this) { ++ idispensebehavior.dispense(source, eventStack); ++ return stack; ++ } ++ } ++ ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); ++ EntityMinecart entityminecart = EntityMinecart.create(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), ((ItemMinecart) itemstack1.getItem()).minecartType); ++ + if (stack.hasDisplayName()) + { + entityminecart.setCustomNameTag(stack.getDisplayName()); + } + +- world.spawnEntity(entityminecart); +- stack.shrink(1); ++ // world.spawnEntity(entityminecart); ++ if (!world.spawnEntity(entityminecart)) stack.grow(1); ++ // stack.shrink(1); // CraftBukkit - handled during event processing + return stack; + } + protected void playDispenseSound(IBlockSource source) +@@ -119,7 +147,9 @@ + entityminecart.setCustomNameTag(itemstack.getDisplayName()); + } + +- worldIn.spawnEntity(entityminecart); ++ // worldIn.spawnEntity(entityminecart); ++ if (!worldIn.spawnEntity(entityminecart)) ++ return EnumActionResult.PASS; + } + + itemstack.shrink(1); diff --git a/patches/net/minecraft/item/ItemMonsterPlacer.java.patch b/patches/net/minecraft/item/ItemMonsterPlacer.java.patch new file mode 100644 index 00000000..3bf9a6f1 --- /dev/null +++ b/patches/net/minecraft/item/ItemMonsterPlacer.java.patch @@ -0,0 +1,32 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemMonsterPlacer.java ++++ ../src-work/minecraft/net/minecraft/item/ItemMonsterPlacer.java +@@ -220,6 +220,11 @@ + @Nullable + public static Entity spawnCreature(World worldIn, @Nullable ResourceLocation entityID, double x, double y, double z) + { ++ return spawnCreature(worldIn, entityID, x, y, z, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); ++ } ++ ++ @Nullable ++ public static Entity spawnCreature(World worldIn, @Nullable ResourceLocation entityID, double x, double y, double z, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { + if (entityID != null && EntityList.ENTITY_EGGS.containsKey(entityID)) + { + Entity entity = null; +@@ -236,8 +241,15 @@ + entityliving.renderYawOffset = entityliving.rotationYaw; + if (net.minecraftforge.event.ForgeEventFactory.doSpecialSpawn(entityliving, worldIn, (float) x, (float) y, (float) z, null)) return null; + entityliving.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityliving)), (IEntityLivingData)null); +- worldIn.spawnEntity(entity); +- entityliving.playLivingSound(); ++ // worldIn.spawnEntity(entity); ++ // entityliving.playLivingSound(); ++ // CraftBukkit start - don't return an entity when CreatureSpawnEvent is canceled ++ if (!worldIn.spawnEntity(entity, spawnReason)) { ++ entity = null; ++ } else { ++ entityliving.playLivingSound(); ++ } ++ // CraftBukkit end + } + } + diff --git a/patches/net/minecraft/item/ItemRecord.java.patch b/patches/net/minecraft/item/ItemRecord.java.patch new file mode 100644 index 00000000..36e50fa7 --- /dev/null +++ b/patches/net/minecraft/item/ItemRecord.java.patch @@ -0,0 +1,10 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemRecord.java ++++ ../src-work/minecraft/net/minecraft/item/ItemRecord.java +@@ -44,6 +44,7 @@ + { + if (!worldIn.isRemote) + { ++ if (true) return EnumActionResult.SUCCESS; // CraftBukkit - handled in ItemStack + ItemStack itemstack = player.getHeldItem(hand); + ((BlockJukebox)Blocks.JUKEBOX).insertRecord(worldIn, pos, iblockstate, itemstack); + worldIn.playEvent((EntityPlayer)null, 1010, pos, Item.getIdFromItem(this)); diff --git a/patches/net/minecraft/item/ItemSkull.java.patch b/patches/net/minecraft/item/ItemSkull.java.patch new file mode 100644 index 00000000..685a3ebe --- /dev/null +++ b/patches/net/minecraft/item/ItemSkull.java.patch @@ -0,0 +1,39 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemSkull.java ++++ ../src-work/minecraft/net/minecraft/item/ItemSkull.java +@@ -11,6 +11,7 @@ + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTUtil; + import net.minecraft.tileentity.TileEntity; + import net.minecraft.tileentity.TileEntitySkull; +@@ -189,12 +190,27 @@ + if (nbt.hasKey("SkullOwner", 8) && !StringUtils.isBlank(nbt.getString("SkullOwner"))) + { + GameProfile gameprofile = new GameProfile((UUID)null, nbt.getString("SkullOwner")); +- gameprofile = TileEntitySkull.updateGameprofile(gameprofile); ++ // Spigot start ++ TileEntitySkull.updateGameprofile(gameprofile, new com.google.common.base.Predicate() { ++ ++ @Override ++ public boolean apply(GameProfile gameprofile) { + nbt.setTag("SkullOwner", NBTUtil.writeGameProfile(new NBTTagCompound(), gameprofile)); ++ return false; ++ } ++ }, false); ++ // Spigot end + return true; + } + else + { ++ NBTTagList textures = nbt.getCompoundTag("SkullOwner").getCompoundTag("Properties").getTagList("textures", 10); // Safe due to method contracts ++ for (int i = 0; i < textures.tagCount(); i++) { ++ if (textures.get(i) instanceof NBTTagCompound && !((NBTTagCompound) textures.get(i)).hasKey("Signature", 8) && ((NBTTagCompound) textures.get(i)).getString("Value").trim().isEmpty()) { ++ nbt.removeTag("SkullOwner"); ++ break; ++ } ++ } + return false; + } + } diff --git a/patches/net/minecraft/item/ItemSnowball.java.patch b/patches/net/minecraft/item/ItemSnowball.java.patch new file mode 100644 index 00000000..25cb001a --- /dev/null +++ b/patches/net/minecraft/item/ItemSnowball.java.patch @@ -0,0 +1,41 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemSnowball.java ++++ ../src-work/minecraft/net/minecraft/item/ItemSnowball.java +@@ -2,6 +2,7 @@ + + import net.minecraft.creativetab.CreativeTabs; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.entity.projectile.EntitySnowball; + import net.minecraft.init.SoundEvents; + import net.minecraft.stats.StatList; +@@ -23,18 +24,28 @@ + { + ItemStack itemstack = playerIn.getHeldItem(handIn); + ++ // CraftBukkit start - moved down ++ /* + if (!playerIn.capabilities.isCreativeMode) + { + itemstack.shrink(1); + } + + worldIn.playSound((EntityPlayer)null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); +- ++ */ + if (!worldIn.isRemote) + { + EntitySnowball entitysnowball = new EntitySnowball(worldIn, playerIn); + entitysnowball.shoot(playerIn, playerIn.rotationPitch, playerIn.rotationYaw, 0.0F, 1.5F, 1.0F); +- worldIn.spawnEntity(entitysnowball); ++ if (worldIn.spawnEntity(entitysnowball)) { ++ if (!playerIn.capabilities.isCreativeMode) { ++ itemstack.shrink(1); ++ } ++ ++ worldIn.playSound(null, playerIn.posX, playerIn.posY, playerIn.posZ, SoundEvents.ENTITY_SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemSnowball.itemRand.nextFloat() * 0.4F + 0.8F)); ++ } else if (playerIn instanceof EntityPlayerMP) { ++ ((EntityPlayerMP) playerIn).getBukkitEntity().updateInventory(); ++ } + } + + playerIn.addStat(StatList.getObjectUseStats(this)); diff --git a/patches/net/minecraft/item/ItemStack.java.patch b/patches/net/minecraft/item/ItemStack.java.patch new file mode 100644 index 00000000..5fbc3765 --- /dev/null +++ b/patches/net/minecraft/item/ItemStack.java.patch @@ -0,0 +1,230 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemStack.java ++++ ../src-work/minecraft/net/minecraft/item/ItemStack.java +@@ -9,7 +9,7 @@ + import java.util.Map.Entry; + import javax.annotation.Nullable; + import net.minecraft.advancements.CriteriaTriggers; +-import net.minecraft.block.Block; ++import net.minecraft.block.*; + import net.minecraft.block.state.IBlockState; + import net.minecraft.client.util.ITooltipFlag; + import net.minecraft.enchantment.Enchantment; +@@ -30,12 +30,16 @@ + import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.stats.StatList; ++import net.minecraft.tileentity.TileEntity; ++import net.minecraft.tileentity.TileEntitySkull; + import net.minecraft.util.ActionResult; + import net.minecraft.util.EnumActionResult; + import net.minecraft.util.EnumFacing; + import net.minecraft.util.EnumHand; + import net.minecraft.util.ResourceLocation; ++import net.minecraft.util.SoundCategory; + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.FixTypes; + import net.minecraft.util.datafix.walkers.BlockEntityTag; +@@ -49,14 +53,21 @@ + import net.minecraft.world.World; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.entity.Player; ++import org.bukkit.event.world.StructureGrowEvent; + + public final class ItemStack implements net.minecraftforge.common.capabilities.ICapabilitySerializable + { + public static final ItemStack EMPTY = new ItemStack((Item)null); + public static final DecimalFormat DECIMALFORMAT = new DecimalFormat("#.##"); +- private int stackSize; ++ public int stackSize; + private int animationsToGo; +- private final Item item; ++ public Item item; + private NBTTagCompound stackTagCompound; + private boolean isEmpty; + int itemDamage; +@@ -119,23 +130,33 @@ + + public ItemStack(NBTTagCompound compound) + { ++ this.load(compound); ++ ++ this.updateEmptyState(); ++ this.forgeInit(); ++ } ++ ++ public void load(NBTTagCompound compound) ++ { + this.capNBT = compound.hasKey("ForgeCaps") ? compound.getCompoundTag("ForgeCaps") : null; + this.item = compound.hasKey("id", 8) ? Item.getByNameOrId(compound.getString("id")) : Items.AIR; //Forge fix tons of NumberFormatExceptions that are caused by deserializing EMPTY ItemStacks. + this.stackSize = compound.getByte("Count"); +- this.itemDamage = Math.max(0, compound.getShort("Damage")); ++ // CraftBukkit start - Route through setData for filtering ++ // this.itemDamage = Math.max(0, compound.getShort("Damage")); ++ this.setItemDamage(compound.getShort("Damage")); + + if (compound.hasKey("tag", 10)) + { +- this.stackTagCompound = compound.getCompoundTag("tag"); ++ // CraftBukkit start - make defensive copy as this data may be coming from the save thread ++ // this.stackTagCompound = compound.getCompoundTag("tag"); ++ this.stackTagCompound = compound.getCompoundTag("tag").copy(); + + if (this.item != null) + { +- this.item.updateItemStackNBT(compound); ++ // this.item.updateItemStackNBT(compound); ++ this.item.updateItemStackNBT(this.stackTagCompound); + } + } +- +- this.updateEmptyState(); +- this.forgeInit(); + } + + public boolean isEmpty() +@@ -160,6 +181,7 @@ + return true; + } + } ++ // Paper end + + public static void registerFixes(DataFixer fixer) + { +@@ -185,11 +207,10 @@ + { + if (!worldIn.isRemote) return net.minecraftforge.common.ForgeHooks.onPlaceItemIntoWorld(this, playerIn, worldIn, pos, side, hitX, hitY, hitZ, hand); + EnumActionResult enumactionresult = this.getItem().onItemUse(playerIn, worldIn, pos, hand, side, hitX, hitY, hitZ); +- + if (enumactionresult == EnumActionResult.SUCCESS) + { +- playerIn.addStat(StatList.getObjectUseStats(this.item)); +- } ++ playerIn.addStat(StatList.getObjectUseStats(this.item)); ++ } + + return enumactionresult; + } +@@ -231,7 +252,8 @@ + + if (this.stackTagCompound != null) + { +- nbt.setTag("tag", this.stackTagCompound); ++ // nbt.setTag("tag", this.stackTagCompound); ++ nbt.setTag("tag", this.stackTagCompound.copy()); // CraftBukkit - make defensive copy, data is going to another thread + } + + if (this.capabilities != null) +@@ -291,6 +313,26 @@ + + public void setItemDamage(int meta) + { ++ // CraftBukkit start - Filter out data for items that shouldn't have it ++ // The crafting system uses this value for a special purpose so we have to allow it ++ if (meta == 32767) { ++ getItem().setDamage(this, meta); ++ return; ++ } ++ ++ // Is this a block? ++ if (CraftMagicNumbers.getBlock(CraftMagicNumbers.getId(this.getItem())) != Blocks.AIR) { ++ // If vanilla doesn't use data on it don't allow any ++ if (!(this.getHasSubtypes() || this.getItem().isDamageable())) { ++ meta = 0; ++ } ++ } ++ ++ // Filter invalid plant data ++ if (CraftMagicNumbers.getBlock(CraftMagicNumbers.getId(this.getItem())) == Blocks.DOUBLE_PLANT && (meta > 5 || meta < 0)) { ++ meta = 0; ++ } ++ // CraftBukkit end + getItem().setDamage(this, meta); + } + +@@ -322,6 +364,19 @@ + + amount -= j; + ++ // Spigot start ++ if (damager != null) { ++ org.bukkit.craftbukkit.inventory.CraftItemStack item = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); ++ org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(damager.getBukkitEntity(), item, amount); ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); ++ if (amount != event.getDamage() || event.isCancelled()) { ++ event.getPlayer().updateInventory(); ++ } ++ if (event.isCancelled()) return false; ++ amount = event.getDamage(); ++ } ++ // Spigot end ++ + if (amount <= 0) + { + return false; +@@ -347,6 +402,11 @@ + if (this.attemptDamageItem(amount, entityIn.getRNG(), entityIn instanceof EntityPlayerMP ? (EntityPlayerMP)entityIn : null)) + { + entityIn.renderBrokenItemStack(this); ++ // CraftBukkit start - Check for item breaking ++ if (this.stackSize == 1 && entityIn instanceof EntityPlayer) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((EntityPlayer) entityIn, this); ++ } ++ // CraftBukkit end + this.shrink(1); + + if (entityIn instanceof EntityPlayer) +@@ -983,6 +1043,14 @@ + + public void setRepairCost(int cost) + { ++ // CraftBukkit start - remove RepairCost tag when 0 (SPIGOT-3945) ++ if (cost == 0) { ++ if (this.hasTagCompound()) { ++ this.stackTagCompound.removeTag("RepairCost"); ++ } ++ return; ++ } ++ // CraftBukkit end + if (!this.hasTagCompound()) + { + this.stackTagCompound = new NBTTagCompound(); +@@ -1043,6 +1111,14 @@ + nbttaglist.appendTag(nbttagcompound); + } + ++ @Deprecated ++ public void setItem(Item item) { ++ this.item = item; ++ // Update delegate as well ++ this.delegate = item.delegate; ++ this.setItemDamage(this.getItemDamage()); // CraftBukkit - Set data again to ensure it is filtered properly ++ } ++ + public ITextComponent getTextComponent() + { + TextComponentString textcomponentstring = new TextComponentString(this.getDisplayName()); +@@ -1280,4 +1356,16 @@ + { + return this.isEmpty() || this.getItem().doesSneakBypassUse(this, world, pos, player); + } ++ ++ // Spigot start ++ public static boolean fastMatches(ItemStack itemstack, ItemStack itemstack1) { ++ if (itemstack == null && itemstack1 == null) { ++ return true; ++ } ++ if (itemstack != null && itemstack1 != null) { ++ return itemstack.stackSize == itemstack1.stackSize && itemstack.item == itemstack1.item && itemstack.itemDamage == itemstack1.itemDamage; ++ } ++ return false; ++ } ++ // Spigot end + } diff --git a/patches/net/minecraft/item/ItemWrittenBook.java.patch b/patches/net/minecraft/item/ItemWrittenBook.java.patch new file mode 100644 index 00000000..732a7881 --- /dev/null +++ b/patches/net/minecraft/item/ItemWrittenBook.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraft/item/ItemWrittenBook.java ++++ ../src-work/minecraft/net/minecraft/item/ItemWrittenBook.java +@@ -10,6 +10,7 @@ + import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTTagString; + import net.minecraft.network.play.server.SPacketSetSlot; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.stats.StatList; + import net.minecraft.util.ActionResult; + import net.minecraft.util.EnumActionResult; +@@ -21,6 +22,7 @@ + import net.minecraft.util.text.TextFormatting; + import net.minecraft.util.text.translation.I18n; + import net.minecraft.world.World; ++import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + +@@ -119,6 +121,24 @@ + String s = nbttaglist.getStringTagAt(i); + ITextComponent itextcomponent; + ++ // CraftBukkit start ++ // Some commands use the worldserver variable but we leave it full of null values, ++ // so we must temporarily populate it with the world of the commandsender ++ WorldServer[] prev = MinecraftServer.getServerCB().worlds; ++ MinecraftServer server = MinecraftServer.getServerCB(); ++ server.worlds = new WorldServer[server.worldServerList.size()]; ++ server.worlds[0] = (WorldServer) player.getEntityWorld(); ++ int bpos = 0; ++ for (int pos = 1; pos < server.worlds.length; pos++) { ++ WorldServer world = server.worldServerList.get(bpos++); ++ if (server.worlds[0] == world) { ++ pos--; ++ continue; ++ } ++ server.worlds[pos] = world; ++ } ++ // CraftBukkit end ++ + try + { + itextcomponent = ITextComponent.Serializer.fromJsonLenient(s); +@@ -128,6 +148,9 @@ + { + itextcomponent = new TextComponentString(s); + } ++ finally { ++ MinecraftServer.getServerCB().worlds = prev; ++ } + + nbttaglist.set(i, new NBTTagString(ITextComponent.Serializer.componentToJson(itextcomponent))); + } diff --git a/patches/net/minecraft/item/crafting/CraftingManager.java.patch b/patches/net/minecraft/item/crafting/CraftingManager.java.patch new file mode 100644 index 00000000..0922bd91 --- /dev/null +++ b/patches/net/minecraft/item/crafting/CraftingManager.java.patch @@ -0,0 +1,47 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/CraftingManager.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/CraftingManager.java +@@ -36,12 +36,13 @@ + { + private static final Logger LOGGER = LogManager.getLogger(); + private static int nextAvailableId; +- public static final RegistryNamespaced REGISTRY = net.minecraftforge.registries.GameData.getWrapper(IRecipe.class); ++ public static RegistryNamespaced REGISTRY = net.minecraftforge.registries.GameData.getWrapper(IRecipe.class); + + public static boolean init() + { + try + { ++ CraftingManager.nextAvailableId = 0; // Reset recipe ID count + register("armordye", new RecipesArmorDyes()); + register("bookcloning", new RecipeBookCloning()); + register("mapcloning", new RecipesMapCloning()); +@@ -188,6 +189,7 @@ + } + else + { ++ recipe.setKey(name); + REGISTRY.register(nextAvailableId++, name, recipe); + } + } +@@ -201,7 +203,7 @@ + return irecipe.getCraftingResult(craftMatrix); + } + } +- ++ craftMatrix.currentRecipe = null; // CraftBukkit - Clear recipe when no recipe is found + return ItemStack.EMPTY; + } + +@@ -212,10 +214,12 @@ + { + if (irecipe.matches(craftMatrix, worldIn)) + { ++ craftMatrix.currentRecipe = irecipe; // CraftBukkit + return irecipe; + } + } + ++ craftMatrix.currentRecipe = null; // CraftBukkit - Clear recipe when no recipe is found + return null; + } + diff --git a/patches/net/minecraft/item/crafting/FurnaceRecipes.java.patch b/patches/net/minecraft/item/crafting/FurnaceRecipes.java.patch new file mode 100644 index 00000000..95e07a0f --- /dev/null +++ b/patches/net/minecraft/item/crafting/FurnaceRecipes.java.patch @@ -0,0 +1,65 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/FurnaceRecipes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/FurnaceRecipes.java +@@ -15,15 +15,18 @@ + public class FurnaceRecipes + { + private static final FurnaceRecipes SMELTING_BASE = new FurnaceRecipes(); +- private final Map smeltingList = Maps.newHashMap(); ++ public Map smeltingList = Maps.newHashMap(); + private final Map experienceList = Maps.newHashMap(); + ++ public Map customRecipes = Maps.newHashMap(); ++ public Map customExperience = Maps.newHashMap(); ++ + public static FurnaceRecipes instance() + { + return SMELTING_BASE; + } + +- private FurnaceRecipes() ++ public FurnaceRecipes() + { + this.addSmeltingRecipeForBlock(Blocks.IRON_ORE, new ItemStack(Items.IRON_INGOT), 0.7F); + this.addSmeltingRecipeForBlock(Blocks.GOLD_ORE, new ItemStack(Items.GOLD_INGOT), 1.0F); +@@ -101,6 +104,11 @@ + this.addSmeltingRecipe(new ItemStack(Blocks.STAINED_HARDENED_CLAY, 1, EnumDyeColor.BLACK.getMetadata()), new ItemStack(Blocks.BLACK_GLAZED_TERRACOTTA), 0.1F); + } + ++ public void registerRecipe(ItemStack itemstack, ItemStack itemstack1, float f) { ++ this.customRecipes.put(itemstack, itemstack1); ++ this.customExperience.put(itemstack, f); ++ } ++ + public void addSmeltingRecipeForBlock(Block input, ItemStack stack, float experience) + { + this.addSmelting(Item.getItemFromBlock(input), stack, experience); +@@ -120,6 +128,14 @@ + + public ItemStack getSmeltingResult(ItemStack stack) + { ++ for (Entry entry : this.customRecipes.entrySet()) ++ { ++ if (this.compareItemStacks(stack, entry.getKey())) ++ { ++ return entry.getValue(); ++ } ++ } ++ + for (Entry entry : this.smeltingList.entrySet()) + { + if (this.compareItemStacks(stack, entry.getKey())) +@@ -146,6 +162,14 @@ + float ret = stack.getItem().getSmeltingExperience(stack); + if (ret != -1) return ret; + ++ for (Entry entry : this.customExperience.entrySet()) ++ { ++ if (this.compareItemStacks(stack, entry.getKey())) ++ { ++ return ((Float)entry.getValue()).floatValue(); ++ } ++ } ++ + for (Entry entry : this.experienceList.entrySet()) + { + if (this.compareItemStacks(stack, entry.getKey())) diff --git a/patches/net/minecraft/item/crafting/IRecipe.java.patch b/patches/net/minecraft/item/crafting/IRecipe.java.patch new file mode 100644 index 00000000..96bef604 --- /dev/null +++ b/patches/net/minecraft/item/crafting/IRecipe.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/IRecipe.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/IRecipe.java +@@ -3,6 +3,7 @@ + import net.minecraft.inventory.InventoryCrafting; + import net.minecraft.item.ItemStack; + import net.minecraft.util.NonNullList; ++import net.minecraft.util.ResourceLocation; + import net.minecraft.world.World; + + public interface IRecipe extends net.minecraftforge.registries.IForgeRegistryEntry +@@ -34,4 +35,14 @@ + { + return ""; + } ++ ++ default org.bukkit.inventory.Recipe toBukkitRecipe() // CraftBukkit ++ { ++ return null; ++ } ++ ++ default void setKey(ResourceLocation key) // CraftBukkit ++ { ++ ++ } + } diff --git a/patches/net/minecraft/item/crafting/Ingredient.java.patch b/patches/net/minecraft/item/crafting/Ingredient.java.patch new file mode 100644 index 00000000..1ca09a43 --- /dev/null +++ b/patches/net/minecraft/item/crafting/Ingredient.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/Ingredient.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/Ingredient.java +@@ -20,7 +20,7 @@ + return p_apply_1_.isEmpty(); + } + }; +- private final ItemStack[] matchingStacks; ++ public final ItemStack[] matchingStacks; + private final ItemStack[] matchingStacksExploded; + private IntList matchingStacksPacked; + private final boolean isSimple; diff --git a/patches/net/minecraft/item/crafting/RecipeBookCloning.java.patch b/patches/net/minecraft/item/crafting/RecipeBookCloning.java.patch new file mode 100644 index 00000000..a5107913 --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipeBookCloning.java.patch @@ -0,0 +1,16 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipeBookCloning.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipeBookCloning.java +@@ -7,8 +7,12 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + +-public class RecipeBookCloning extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipeBookCloning extends ShapelessRecipes implements IRecipe + { ++ public RecipeBookCloning() { ++ super("", new ItemStack(Items.WRITTEN_BOOK, 0, -1), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.WRITABLE_BOOK))); ++ } ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + int i = 0; diff --git a/patches/net/minecraft/item/crafting/RecipeFireworks.java.patch b/patches/net/minecraft/item/crafting/RecipeFireworks.java.patch new file mode 100644 index 00000000..df746e71 --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipeFireworks.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipeFireworks.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipeFireworks.java +@@ -11,10 +11,16 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + +-public class RecipeFireworks extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipeFireworks extends ShapelessRecipes implements IRecipe + { + private ItemStack resultItem = ItemStack.EMPTY; + ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public RecipeFireworks() { ++ super("", new ItemStack(Items.FIREWORKS, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.GUNPOWDER))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + this.resultItem = ItemStack.EMPTY; diff --git a/patches/net/minecraft/item/crafting/RecipeRepairItem.java.patch b/patches/net/minecraft/item/crafting/RecipeRepairItem.java.patch new file mode 100644 index 00000000..e131717a --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipeRepairItem.java.patch @@ -0,0 +1,47 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipeRepairItem.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipeRepairItem.java +@@ -2,14 +2,22 @@ + + import com.google.common.collect.Lists; + import java.util.List; ++ ++import net.minecraft.init.Items; + import net.minecraft.inventory.InventoryCrafting; +-import net.minecraft.item.Item; + import net.minecraft.item.ItemStack; + import net.minecraft.util.NonNullList; ++import net.minecraft.util.ResourceLocation; + import net.minecraft.world.World; + +-public class RecipeRepairItem extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipeRepairItem extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class ++ public RecipeRepairItem() { ++ super("", new ItemStack(Items.LEATHER_HELMET), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.LEATHER_HELMET))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + List list = Lists.newArrayList(); +@@ -79,7 +87,18 @@ + i1 = 0; + } + +- return new ItemStack(itemstack2.getItem(), 1, i1); ++ // CraftBukkit start - Construct a dummy repair recipe ++ ItemStack result = new ItemStack(itemstack3.getItem(), 1, i1); ++ NonNullList ingredients = NonNullList.create(); ++ ingredients.add(Ingredient.fromStacks(new ItemStack[]{itemstack2.copy()})); ++ ingredients.add(Ingredient.fromStacks(new ItemStack[]{itemstack3.copy()})); ++ ShapelessRecipes recipe = new ShapelessRecipes("", result.copy(), ingredients); ++ recipe.key = new ResourceLocation("repairitem"); ++ inv.currentRecipe = recipe; ++ result = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(inv, result, inv.eventHandler.getBukkitView(), true); ++ return result; ++ // return new ItemStack(itemstack2.getItem(), 1, i1); ++ // CraftBukkit end + } + } + diff --git a/patches/net/minecraft/item/crafting/RecipeTippedArrow.java.patch b/patches/net/minecraft/item/crafting/RecipeTippedArrow.java.patch new file mode 100644 index 00000000..0fbd4d82 --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipeTippedArrow.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipeTippedArrow.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipeTippedArrow.java +@@ -8,8 +8,16 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + +-public class RecipeTippedArrow extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipeTippedArrow extends ShapedRecipes implements IRecipe + { ++ public RecipeTippedArrow() { ++ super("", 3, 3, NonNullList.from(Ingredient.EMPTY, ++ Ingredient.fromItem(Items.ARROW), Ingredient.fromItem(Items.ARROW), Ingredient.fromItem(Items.ARROW), ++ Ingredient.fromItem(Items.ARROW), Ingredient.fromItem(Items.LINGERING_POTION), Ingredient.fromItem(Items.ARROW), ++ Ingredient.fromItem(Items.ARROW), Ingredient.fromItem(Items.ARROW), Ingredient.fromItem(Items.ARROW)), ++ new ItemStack(Items.TIPPED_ARROW, 8)); ++ } ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + if (inv.getWidth() == 3 && inv.getHeight() == 3) diff --git a/patches/net/minecraft/item/crafting/RecipesArmorDyes.java.patch b/patches/net/minecraft/item/crafting/RecipesArmorDyes.java.patch new file mode 100644 index 00000000..644e2571 --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipesArmorDyes.java.patch @@ -0,0 +1,24 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipesArmorDyes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipesArmorDyes.java +@@ -4,14 +4,19 @@ + import java.util.List; + import net.minecraft.init.Items; + import net.minecraft.inventory.InventoryCrafting; +-import net.minecraft.item.EnumDyeColor; + import net.minecraft.item.ItemArmor; + import net.minecraft.item.ItemStack; + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + +-public class RecipesArmorDyes extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipesArmorDyes extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public RecipesArmorDyes() { ++ super("", new ItemStack(Items.LEATHER_HELMET, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.DYE))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + ItemStack itemstack = ItemStack.EMPTY; diff --git a/patches/net/minecraft/item/crafting/RecipesBanners.java.patch b/patches/net/minecraft/item/crafting/RecipesBanners.java.patch new file mode 100644 index 00000000..22b91d08 --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipesBanners.java.patch @@ -0,0 +1,34 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipesBanners.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipesBanners.java +@@ -15,8 +15,14 @@ + + public class RecipesBanners + { +- public static class RecipeAddPattern extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++ public static class RecipeAddPattern extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public RecipeAddPattern() { ++ super("", new ItemStack(Items.BANNER, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.BANNER))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + boolean flag = false; +@@ -238,8 +244,14 @@ + } + } + +- public static class RecipeDuplicatePattern extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++ public static class RecipeDuplicatePattern extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public RecipeDuplicatePattern() { ++ super("", new ItemStack(Items.BANNER, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.DYE))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + ItemStack itemstack = ItemStack.EMPTY; diff --git a/patches/net/minecraft/item/crafting/RecipesMapCloning.java.patch b/patches/net/minecraft/item/crafting/RecipesMapCloning.java.patch new file mode 100644 index 00000000..94c3cd1c --- /dev/null +++ b/patches/net/minecraft/item/crafting/RecipesMapCloning.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/RecipesMapCloning.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/RecipesMapCloning.java +@@ -6,8 +6,14 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + +-public class RecipesMapCloning extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++public class RecipesMapCloning extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class ++ public RecipesMapCloning() { ++ super("", new ItemStack(Items.MAP, 0, -1), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.MAP))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + int i = 0; diff --git a/patches/net/minecraft/item/crafting/ShapedRecipes.java.patch b/patches/net/minecraft/item/crafting/ShapedRecipes.java.patch new file mode 100644 index 00000000..4798a2ec --- /dev/null +++ b/patches/net/minecraft/item/crafting/ShapedRecipes.java.patch @@ -0,0 +1,89 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/ShapedRecipes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/ShapedRecipes.java +@@ -19,6 +19,9 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.util.ResourceLocation; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; ++import org.bukkit.inventory.Recipe; + + public class ShapedRecipes extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements net.minecraftforge.common.crafting.IShapedRecipe + { +@@ -28,6 +31,8 @@ + private final ItemStack recipeOutput; + private final String group; + ++ public ResourceLocation key; ++ + public ShapedRecipes(String group, int width, int height, NonNullList ingredients, ItemStack result) + { + this.group = group; +@@ -373,6 +378,67 @@ + } + } + ++ @Override ++ public Recipe toBukkitRecipe() { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.recipeOutput); ++ CraftShapedRecipe recipe = new CraftShapedRecipe(result, this); ++ switch (this.recipeHeight) { ++ case 1: ++ switch (this.recipeWidth) { ++ case 1: ++ recipe.shape("a"); ++ break; ++ case 2: ++ recipe.shape("ab"); ++ break; ++ case 3: ++ recipe.shape("abc"); ++ break; ++ } ++ break; ++ case 2: ++ switch (this.recipeWidth) { ++ case 1: ++ recipe.shape("a","b"); ++ break; ++ case 2: ++ recipe.shape("ab","cd"); ++ break; ++ case 3: ++ recipe.shape("abc","def"); ++ break; ++ } ++ break; ++ case 3: ++ switch (this.recipeWidth) { ++ case 1: ++ recipe.shape("a","b","c"); ++ break; ++ case 2: ++ recipe.shape("ab","cd","ef"); ++ break; ++ case 3: ++ recipe.shape("abc","def","ghi"); ++ break; ++ } ++ break; ++ } ++ char c = 'a'; ++ for (Ingredient list : this.recipeItems) { ++ if (list != null && list.matchingStacks.length > 0) { ++ net.minecraft.item.ItemStack stack = list.matchingStacks[0]; ++ recipe.setIngredient(c, org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(stack.getItem()), (list.matchingStacks.length) > 1 ? 32767 : stack.getMetadata()); ++ } ++ c++; ++ } ++ return recipe; ++ } ++ ++ @Override ++ public void setKey(ResourceLocation key) { ++ this.key = key; ++ } ++ + //================================================ FORGE START ================================================ + @Override + public int getRecipeWidth() diff --git a/patches/net/minecraft/item/crafting/ShapelessRecipes.java.patch b/patches/net/minecraft/item/crafting/ShapelessRecipes.java.patch new file mode 100644 index 00000000..2a067614 --- /dev/null +++ b/patches/net/minecraft/item/crafting/ShapelessRecipes.java.patch @@ -0,0 +1,46 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/ShapelessRecipes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/ShapelessRecipes.java +@@ -9,7 +9,11 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.util.JsonUtils; + import net.minecraft.util.NonNullList; ++import net.minecraft.util.ResourceLocation; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; ++import org.bukkit.inventory.Recipe; + + public class ShapelessRecipes extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe + { +@@ -18,6 +22,8 @@ + private final String group; + private final boolean isSimple; + ++ public ResourceLocation key; ++ + public ShapelessRecipes(String group, ItemStack output, NonNullList ingredients) + { + this.group = group; +@@ -136,4 +142,22 @@ + { + return width * height >= this.recipeItems.size(); + } ++ ++ @Override ++ public Recipe toBukkitRecipe() { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.recipeOutput); ++ CraftShapelessRecipe recipe = new CraftShapelessRecipe(result, this); ++ for (Ingredient list : this.recipeItems) { ++ if (list != null) { ++ net.minecraft.item.ItemStack stack = list.matchingStacks[0]; ++ recipe.addIngredient(org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(stack.getItem()), (list.matchingStacks.length) > 1 ? 32767 : stack.getMetadata()); ++ } ++ } ++ return recipe; ++ } ++ ++ @Override ++ public void setKey(ResourceLocation key) { ++ this.key = key; ++ } + } diff --git a/patches/net/minecraft/item/crafting/ShieldRecipes.java.patch b/patches/net/minecraft/item/crafting/ShieldRecipes.java.patch new file mode 100644 index 00000000..43ac958e --- /dev/null +++ b/patches/net/minecraft/item/crafting/ShieldRecipes.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/ShieldRecipes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/ShieldRecipes.java +@@ -9,8 +9,14 @@ + + public class ShieldRecipes + { +- public static class Decoration extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++ public static class Decoration extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public Decoration() { ++ super("", new ItemStack(Items.SHIELD, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.BANNER))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + ItemStack itemstack = ItemStack.EMPTY; diff --git a/patches/net/minecraft/item/crafting/ShulkerBoxRecipes.java.patch b/patches/net/minecraft/item/crafting/ShulkerBoxRecipes.java.patch new file mode 100644 index 00000000..c76b1363 --- /dev/null +++ b/patches/net/minecraft/item/crafting/ShulkerBoxRecipes.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/item/crafting/ShulkerBoxRecipes.java ++++ ../src-work/minecraft/net/minecraft/item/crafting/ShulkerBoxRecipes.java +@@ -2,17 +2,23 @@ + + import net.minecraft.block.Block; + import net.minecraft.block.BlockShulkerBox; ++import net.minecraft.init.Blocks; + import net.minecraft.init.Items; + import net.minecraft.inventory.InventoryCrafting; +-import net.minecraft.item.EnumDyeColor; + import net.minecraft.item.ItemStack; + import net.minecraft.util.NonNullList; + import net.minecraft.world.World; + + public class ShulkerBoxRecipes + { +- public static class ShulkerBoxColoring extends net.minecraftforge.registries.IForgeRegistryEntry.Impl implements IRecipe ++ public static class ShulkerBoxColoring extends ShapelessRecipes implements IRecipe + { ++ // CraftBukkit start - Delegate to new parent class with bogus info ++ public ShulkerBoxColoring() { ++ super("", new ItemStack(Blocks.WHITE_SHULKER_BOX, 0, 0), NonNullList.from(Ingredient.EMPTY, Ingredient.fromItem(Items.DYE))); ++ } ++ // CraftBukkit end ++ + public boolean matches(InventoryCrafting inv, World worldIn) + { + int i = 0; diff --git a/patches/net/minecraft/nbt/CompressedStreamTools.java.patch b/patches/net/minecraft/nbt/CompressedStreamTools.java.patch new file mode 100644 index 00000000..7d1d095a --- /dev/null +++ b/patches/net/minecraft/nbt/CompressedStreamTools.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraft/nbt/CompressedStreamTools.java ++++ ../src-work/minecraft/net/minecraft/nbt/CompressedStreamTools.java +@@ -85,6 +85,12 @@ + + public static NBTTagCompound read(DataInput input, NBTSizeTracker accounter) throws IOException + { ++ // Spigot start ++ if ( input instanceof io.netty.buffer.ByteBufInputStream ) ++ { ++ input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, accounter)); ++ } ++ // Spigot end + NBTBase nbtbase = read(input, 0, accounter); + + if (nbtbase instanceof NBTTagCompound) diff --git a/patches/net/minecraft/nbt/NBTBase.java.patch b/patches/net/minecraft/nbt/NBTBase.java.patch new file mode 100644 index 00000000..bfd17d00 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTBase.java.patch @@ -0,0 +1,64 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTBase.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTBase.java +@@ -8,7 +8,7 @@ + { + public static final String[] NBT_TYPES = new String[] {"END", "BYTE", "SHORT", "INT", "LONG", "FLOAT", "DOUBLE", "BYTE[]", "STRING", "LIST", "COMPOUND", "INT[]", "LONG[]"}; + +- abstract void write(DataOutput output) throws IOException; ++ public abstract void write(DataOutput output) throws IOException; + + abstract void read(DataInput input, int depth, NBTSizeTracker sizeTracker) throws IOException; + +@@ -88,6 +88,52 @@ + } + } + ++ public boolean isEnd() { ++ return getId() == 0; ++ } ++ ++ public boolean isByte() { ++ return getId() == 1; ++ } ++ ++ public boolean isShort() { ++ return getId() == 2; ++ } ++ public boolean isInt() { ++ return getId() == 3; ++ } ++ public boolean isLong() { ++ return getId() == 4; ++ } ++ public boolean isFloat() { ++ return getId() == 5; ++ } ++ public boolean isDouble() { ++ return getId() == 6; ++ } ++ public boolean isByteArray() { ++ return getId() == 7; ++ } ++ public boolean isString() { ++ return getId() == 8; ++ } ++ public boolean isList() { ++ return getId() == 9; ++ } ++ public boolean isCompound() { ++ return getId() == 10; ++ } ++ public boolean isIntArr() { ++ return getId() == 11; ++ } ++ public boolean isLongArr() { ++ return getId() == 12; ++ } ++ public boolean isNumber() { ++ int id = getId(); ++ return id >= 1 && id <= 6; ++ } ++ + public abstract NBTBase copy(); + + public boolean hasNoTags() diff --git a/patches/net/minecraft/nbt/NBTTagByte.java.patch b/patches/net/minecraft/nbt/NBTTagByte.java.patch new file mode 100644 index 00000000..a993cbb6 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagByte.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagByte.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagByte.java +@@ -17,7 +17,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeByte(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagByteArray.java.patch b/patches/net/minecraft/nbt/NBTTagByteArray.java.patch new file mode 100644 index 00000000..33708fb7 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagByteArray.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagByteArray.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagByteArray.java +@@ -37,7 +37,7 @@ + return abyte; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeInt(this.data.length); + output.write(this.data); diff --git a/patches/net/minecraft/nbt/NBTTagCompound.java.patch b/patches/net/minecraft/nbt/NBTTagCompound.java.patch new file mode 100644 index 00000000..284fed92 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagCompound.java.patch @@ -0,0 +1,382 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagCompound.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagCompound.java +@@ -5,13 +5,7 @@ + import java.io.DataInput; + import java.io.DataOutput; + import java.io.IOException; +-import java.util.Collection; +-import java.util.Collections; +-import java.util.List; +-import java.util.Map; +-import java.util.Objects; +-import java.util.Set; +-import java.util.UUID; ++import java.util.*; + import java.util.regex.Pattern; + import javax.annotation.Nullable; + import net.minecraft.crash.CrashReport; +@@ -25,14 +19,12 @@ + { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Pattern SIMPLE_VALUE = Pattern.compile("[A-Za-z0-9._+-]+"); +- private final Map tagMap = Maps.newHashMap(); ++ private Map tagMap = Maps.newHashMap(); // Paper + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { +- for (String s : this.tagMap.keySet()) +- { +- NBTBase nbtbase = this.tagMap.get(s); +- writeEntry(s, nbtbase, output); ++ for (Map.Entry entry : this.tagMap.entrySet()) { ++ writeEntry(entry.getKey(), entry.getValue(), output); + } + + output.writeByte(0); +@@ -70,6 +62,16 @@ + return this.tagMap.keySet(); + } + ++ public Collection values() ++ { ++ return this.tagMap.values(); ++ } ++ ++ public Set> entrySet() ++ { ++ return this.tagMap.entrySet(); ++ } ++ + public byte getId() + { + return 10; +@@ -82,7 +84,6 @@ + + public void setTag(String key, NBTBase value) + { +- if (value == null) throw new IllegalArgumentException("Invalid null NBT value with key " + key); + this.tagMap.put(key, value); + } + +@@ -183,188 +184,77 @@ + } + else + { +- return i == 1 || i == 2 || i == 3 || i == 4 || i == 5 || i == 6; ++ return i >= 1 && i <= 6; + } + } + + public byte getByte(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getByte(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getByte() : 0; + } + + public short getShort(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getShort(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getShort() : 0; + } + + public int getInteger(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getInt(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getInt() : 0; + } + + public long getLong(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getLong(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0L; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getLong() : 0; + } + + public float getFloat(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getFloat(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0.0F; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getFloat() : 0; + } + + public double getDouble(String key) + { +- try +- { +- if (this.hasKey(key, 99)) +- { +- return ((NBTPrimitive)this.tagMap.get(key)).getDouble(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return 0.0D; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTPrimitive ? ((NBTPrimitive)tag).getDouble() : 0; + } + + public String getString(String key) + { +- try +- { +- if (this.hasKey(key, 8)) +- { +- return ((NBTBase)this.tagMap.get(key)).getString(); +- } +- } +- catch (ClassCastException var3) +- { +- ; +- } +- +- return ""; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTTagString ? (tag).getString() : ""; + } + + public byte[] getByteArray(String key) + { +- try +- { +- if (this.hasKey(key, 7)) +- { +- return ((NBTTagByteArray)this.tagMap.get(key)).getByteArray(); +- } +- } +- catch (ClassCastException classcastexception) +- { +- throw new ReportedException(this.createCrashReport(key, 7, classcastexception)); +- } +- +- return new byte[0]; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTTagByteArray ? ((NBTTagByteArray)tag).getByteArray() : new byte[0]; + } + + public int[] getIntArray(String key) + { +- try +- { +- if (this.hasKey(key, 11)) +- { +- return ((NBTTagIntArray)this.tagMap.get(key)).getIntArray(); +- } +- } +- catch (ClassCastException classcastexception) +- { +- throw new ReportedException(this.createCrashReport(key, 11, classcastexception)); +- } +- +- return new int[0]; ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTTagIntArray ? ((NBTTagIntArray)tag).getIntArray() : new int[0]; + } + + public NBTTagCompound getCompoundTag(String key) + { +- try +- { +- if (this.hasKey(key, 10)) +- { +- return (NBTTagCompound)this.tagMap.get(key); +- } +- } +- catch (ClassCastException classcastexception) +- { +- throw new ReportedException(this.createCrashReport(key, 10, classcastexception)); +- } +- +- return new NBTTagCompound(); ++ NBTBase tag = this.tagMap.get(key); ++ return tag instanceof NBTTagCompound ? ((NBTTagCompound)tag) : new NBTTagCompound(); + } + + public NBTTagList getTagList(String key, int type) + { +- try +- { +- if (this.getTagId(key) == 9) +- { +- NBTTagList nbttaglist = (NBTTagList)this.tagMap.get(key); +- ++ NBTBase tag = this.tagMap.get(key); ++ if (tag == null) { ++ return new NBTTagList(); ++ } ++ NBTTagList nbttaglist = (NBTTagList) tag; + if (!nbttaglist.hasNoTags() && nbttaglist.getTagType() != type) + { + return new NBTTagList(); +@@ -372,15 +262,7 @@ + + return nbttaglist; + } +- } +- catch (ClassCastException classcastexception) +- { +- throw new ReportedException(this.createCrashReport(key, 9, classcastexception)); +- } + +- return new NBTTagList(); +- } +- + public boolean getBoolean(String key) + { + return this.getByte(key) != 0; +@@ -394,23 +276,13 @@ + public String toString() + { + StringBuilder stringbuilder = new StringBuilder("{"); +- Collection collection = this.tagMap.keySet(); + +- if (LOGGER.isDebugEnabled()) +- { +- List list = Lists.newArrayList(this.tagMap.keySet()); +- Collections.sort(list); +- collection = list; +- } +- +- for (String s : collection) +- { +- if (stringbuilder.length() != 1) +- { +- stringbuilder.append(','); ++ for (Map.Entry entry : this.tagMap.entrySet()) { ++ stringbuilder.append(handleEscape(entry.getKey())).append(':').append(entry.getValue()).append(','); + } + +- stringbuilder.append(handleEscape(s)).append(':').append(this.tagMap.get(s)); ++ if (stringbuilder.charAt(stringbuilder.length() - 1) == ',') { ++ stringbuilder.deleteCharAt(stringbuilder.length() - 1); + } + + return stringbuilder.append('}').toString(); +@@ -446,10 +318,10 @@ + public NBTTagCompound copy() + { + NBTTagCompound nbttagcompound = new NBTTagCompound(); +- +- for (String s : this.tagMap.keySet()) ++ nbttagcompound.tagMap = new HashMap((int)(this.tagMap.size()*1.35)+1); //compaction factor ++ for (Map.Entry s : this.tagMap.entrySet()) + { +- nbttagcompound.setTag(s, ((NBTBase)this.tagMap.get(s)).copy()); ++ nbttagcompound.setTag(s.getKey(), s.getValue().copy()); + } + + return nbttagcompound; +@@ -509,26 +381,30 @@ + + public void merge(NBTTagCompound other) + { +- for (String s : other.tagMap.keySet()) +- { +- NBTBase nbtbase = other.tagMap.get(s); +- +- if (nbtbase.getId() == 10) +- { +- if (this.hasKey(s, 10)) +- { +- NBTTagCompound nbttagcompound = this.getCompoundTag(s); +- nbttagcompound.merge((NBTTagCompound)nbtbase); ++ for (Map.Entry otherEntry : other.tagMap.entrySet()) { ++ String k = otherEntry.getKey(); ++ NBTBase v = otherEntry.getValue(); ++ if (v.getId() == 10) { ++// NBTTagCompound ++ NBTBase tv = this.tagMap.get(k); ++ if (tv != null && tv.getId() == 10) { ++ NBTTagCompound nbttagcompound = (NBTTagCompound) tv; ++ nbttagcompound.merge((NBTTagCompound) v); ++ } else { ++ this.setTag(k, v.copy()); + } +- else +- { +- this.setTag(s, nbtbase.copy()); ++ } else { ++ this.setTag(k, v.copy()); ++ } + } + } +- else +- { +- this.setTag(s, nbtbase.copy()); +- } ++ ++ public T getTagIfPresent(String key, Class type) { ++ NBTBase tag = this.tagMap.get(key); ++ if (tag != null && tag.getClass() == type) { ++ return (T) tag; ++ } else { ++ return null; + } + } + diff --git a/patches/net/minecraft/nbt/NBTTagDouble.java.patch b/patches/net/minecraft/nbt/NBTTagDouble.java.patch new file mode 100644 index 00000000..1977dc3a --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagDouble.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagDouble.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagDouble.java +@@ -18,7 +18,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeDouble(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagEnd.java.patch b/patches/net/minecraft/nbt/NBTTagEnd.java.patch new file mode 100644 index 00000000..a617dbf6 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagEnd.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagEnd.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagEnd.java +@@ -11,7 +11,7 @@ + sizeTracker.read(64L); + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + } + diff --git a/patches/net/minecraft/nbt/NBTTagFloat.java.patch b/patches/net/minecraft/nbt/NBTTagFloat.java.patch new file mode 100644 index 00000000..0c676ec9 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagFloat.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagFloat.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagFloat.java +@@ -18,7 +18,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeFloat(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagInt.java.patch b/patches/net/minecraft/nbt/NBTTagInt.java.patch new file mode 100644 index 00000000..fa5efc87 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagInt.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagInt.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagInt.java +@@ -17,7 +17,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeInt(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagIntArray.java.patch b/patches/net/minecraft/nbt/NBTTagIntArray.java.patch new file mode 100644 index 00000000..e2737124 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagIntArray.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagIntArray.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagIntArray.java +@@ -37,7 +37,7 @@ + return aint; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeInt(this.intArray.length); + diff --git a/patches/net/minecraft/nbt/NBTTagList.java.patch b/patches/net/minecraft/nbt/NBTTagList.java.patch new file mode 100644 index 00000000..a65926de --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagList.java.patch @@ -0,0 +1,39 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagList.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagList.java +@@ -4,6 +4,7 @@ + import java.io.DataInput; + import java.io.DataOutput; + import java.io.IOException; ++import java.util.ArrayList; + import java.util.List; + import java.util.Objects; + import org.apache.logging.log4j.LogManager; +@@ -12,10 +13,16 @@ + public class NBTTagList extends NBTBase implements java.lang.Iterable + { + private static final Logger LOGGER = LogManager.getLogger(); +- private List tagList = Lists.newArrayList(); ++ public List tagList = Lists.newArrayList(); // Paper ++ // Paper start ++ public void sort(java.util.Comparator comparator) { ++ //noinspection unchecked ++ java.util.Collections.sort(tagList, (java.util.Comparator) comparator); ++ } ++ // Paper end + private byte tagType = 0; + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + if (this.tagList.isEmpty()) + { +@@ -249,7 +256,8 @@ + { + NBTTagList nbttaglist = new NBTTagList(); + nbttaglist.tagType = this.tagType; +- ++ if ( nbttaglist.tagList instanceof ArrayList) // Kettle, ensure we dont create arrays to then delete them ++ ((ArrayList)nbttaglist.tagList).ensureCapacity(this.tagList.size()); + for (NBTBase nbtbase : this.tagList) + { + NBTBase nbtbase1 = nbtbase.copy(); diff --git a/patches/net/minecraft/nbt/NBTTagLong.java.patch b/patches/net/minecraft/nbt/NBTTagLong.java.patch new file mode 100644 index 00000000..4674fbfa --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagLong.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagLong.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagLong.java +@@ -17,7 +17,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeLong(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagLongArray.java.patch b/patches/net/minecraft/nbt/NBTTagLongArray.java.patch new file mode 100644 index 00000000..2572f3b0 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagLongArray.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagLongArray.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagLongArray.java +@@ -37,7 +37,7 @@ + return along; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeInt(this.data.length); + diff --git a/patches/net/minecraft/nbt/NBTTagShort.java.patch b/patches/net/minecraft/nbt/NBTTagShort.java.patch new file mode 100644 index 00000000..4a8bc428 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagShort.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagShort.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagShort.java +@@ -17,7 +17,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeShort(this.data); + } diff --git a/patches/net/minecraft/nbt/NBTTagString.java.patch b/patches/net/minecraft/nbt/NBTTagString.java.patch new file mode 100644 index 00000000..9ddb2767 --- /dev/null +++ b/patches/net/minecraft/nbt/NBTTagString.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/nbt/NBTTagString.java ++++ ../src-work/minecraft/net/minecraft/nbt/NBTTagString.java +@@ -20,7 +20,7 @@ + this.data = data; + } + +- void write(DataOutput output) throws IOException ++ public void write(DataOutput output) throws IOException + { + output.writeUTF(this.data); + } diff --git a/patches/net/minecraft/network/NetHandlerPlayServer.java.patch b/patches/net/minecraft/network/NetHandlerPlayServer.java.patch new file mode 100644 index 00000000..23ceb4a8 --- /dev/null +++ b/patches/net/minecraft/network/NetHandlerPlayServer.java.patch @@ -0,0 +1,1867 @@ +--- ../src-base/minecraft/net/minecraft/network/NetHandlerPlayServer.java ++++ ../src-work/minecraft/net/minecraft/network/NetHandlerPlayServer.java +@@ -3,13 +3,16 @@ + import com.google.common.collect.Lists; + import com.google.common.primitives.Doubles; + import com.google.common.primitives.Floats; +-import com.google.common.util.concurrent.Futures; + import io.netty.util.concurrent.Future; + import io.netty.util.concurrent.GenericFutureListener; + import java.io.IOException; + import java.util.Collections; ++import java.util.HashSet; + import java.util.List; + import java.util.Set; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; ++ + import net.minecraft.advancements.Advancement; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.block.BlockCommandBlock; +@@ -19,6 +22,7 @@ + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.crash.ICrashReportDetail; + import net.minecraft.entity.Entity; ++import net.minecraft.entity.EntityLiving; + import net.minecraft.entity.IJumpingMount; + import net.minecraft.entity.MoverType; + import net.minecraft.entity.item.EntityBoat; +@@ -40,6 +44,7 @@ + import net.minecraft.inventory.EntityEquipmentSlot; + import net.minecraft.inventory.IInventory; + import net.minecraft.inventory.Slot; ++import net.minecraft.item.Item; + import net.minecraft.item.ItemElytra; + import net.minecraft.item.ItemStack; + import net.minecraft.item.ItemWritableBook; +@@ -82,12 +87,14 @@ + import net.minecraft.network.play.server.SPacketChat; + import net.minecraft.network.play.server.SPacketConfirmTransaction; + import net.minecraft.network.play.server.SPacketDisconnect; ++import net.minecraft.network.play.server.SPacketEntityAttach; ++import net.minecraft.network.play.server.SPacketEntityMetadata; + import net.minecraft.network.play.server.SPacketHeldItemChange; + import net.minecraft.network.play.server.SPacketKeepAlive; + import net.minecraft.network.play.server.SPacketMoveVehicle; + import net.minecraft.network.play.server.SPacketPlayerPosLook; +-import net.minecraft.network.play.server.SPacketRespawn; + import net.minecraft.network.play.server.SPacketSetSlot; ++import net.minecraft.network.play.server.SPacketSpawnPosition; + import net.minecraft.network.play.server.SPacketTabComplete; + import net.minecraft.server.MinecraftServer; + import net.minecraft.tileentity.CommandBlockBaseLogic; +@@ -108,6 +115,7 @@ + import net.minecraft.util.ServerRecipeBookHelper; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; ++import net.minecraft.util.math.RayTraceResult; + import net.minecraft.util.math.Vec3d; + import net.minecraft.util.text.ChatType; + import net.minecraft.util.text.ITextComponent; +@@ -120,6 +128,43 @@ + import org.apache.commons.lang3.StringUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.block.Action; ++import org.bukkit.event.block.SignChangeEvent; ++import org.bukkit.event.inventory.ClickType; ++import org.bukkit.event.inventory.CraftItemEvent; ++import org.bukkit.event.inventory.InventoryAction; ++import org.bukkit.event.inventory.InventoryClickEvent; ++import org.bukkit.event.inventory.InventoryCreativeEvent; ++import org.bukkit.event.inventory.InventoryType; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerAnimationEvent; ++import org.bukkit.event.player.PlayerChatEvent; ++import org.bukkit.event.player.PlayerCommandPreprocessEvent; ++import org.bukkit.event.player.PlayerInteractAtEntityEvent; ++import org.bukkit.event.player.PlayerInteractEntityEvent; ++import org.bukkit.event.player.PlayerItemHeldEvent; ++import org.bukkit.event.player.PlayerKickEvent; ++import org.bukkit.event.player.PlayerMoveEvent; ++import org.bukkit.event.player.PlayerResourcePackStatusEvent; ++import org.bukkit.event.player.PlayerSwapHandItemsEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.event.player.PlayerToggleFlightEvent; ++import org.bukkit.event.player.PlayerToggleSneakEvent; ++import org.bukkit.event.player.PlayerToggleSprintEvent; ++import org.bukkit.inventory.CraftingInventory; ++import org.bukkit.inventory.EquipmentSlot; ++import org.bukkit.inventory.InventoryView; ++import org.bukkit.util.NumberConversions; + + public class NetHandlerPlayServer implements INetHandlerPlayServer, ITickable + { +@@ -131,7 +176,11 @@ + private long field_194402_f; + private boolean field_194403_g; + private long field_194404_h; +- private int chatSpamThresholdCount; ++ // private int chatSpamThresholdCount; ++ // CraftBukkit start - multithreaded fields ++ private volatile int chatThrottle; ++ private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(NetHandlerPlayServer.class, "chatThrottle"); ++ // CraftBukkit end + private int itemDropThreshold; + private final IntHashMap pendingTransactions = new IntHashMap(); + private double firstGoodX; +@@ -158,6 +207,25 @@ + private int lastMovePacketCounter; + private ServerRecipeBookHelper field_194309_H = new ServerRecipeBookHelper(); + ++ private final org.bukkit.craftbukkit.CraftServer server; ++ private boolean processedDisconnect; ++ private int lastTick = MinecraftServer.currentTick; ++ private int allowedPlayerTicks = 1; ++ private int lastDropTick = MinecraftServer.currentTick; ++ private int lastBookTick = MinecraftServer.currentTick; ++ private int dropCount = 0; ++ private static final int SURVIVAL_PLACE_DISTANCE_SQUARED = 6 * 6; ++ private static final int CREATIVE_PLACE_DISTANCE_SQUARED = 7 * 7; ++ ++ // Get position of last block hit for BlockDamageLevel.STOPPED ++ private double lastPosX = Double.MAX_VALUE; ++ private double lastPosY = Double.MAX_VALUE; ++ private double lastPosZ = Double.MAX_VALUE; ++ private float lastPitch = Float.MAX_VALUE; ++ private float lastYaw = Float.MAX_VALUE; ++ private boolean justTeleported = false; ++ private final static HashSet invalidItems = new HashSet(java.util.Arrays.asList(8, 9, 10, 11, 26, 34, 36, 43, 51, 55, 59, 62, 63, 64, 68, 71, 74, 75, 83, 90, 92, 93, 94, 104, 105, 115, 117, 118, 119, 125, 127, 132, 140, 141, 142, 144)); // TODO: Check after every update. ++ + public NetHandlerPlayServer(MinecraftServer server, NetworkManager networkManagerIn, EntityPlayerMP playerIn) + { + this.serverController = server; +@@ -165,8 +233,13 @@ + networkManagerIn.setNetHandler(this); + this.player = playerIn; + playerIn.connection = this; ++ this.server = serverController.server; + } + ++ public CraftPlayer getPlayer() { ++ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity(); ++ } ++ + public void update() + { + this.captureCurrentPosition(); +@@ -226,7 +299,7 @@ + this.serverController.profiler.startSection("keepAlive"); + long i = this.currentTimeMillis(); + +- if (i - this.field_194402_f >= 15000L) ++ if (i - this.field_194402_f >= 25000L) // CraftBukkit + { + if (this.field_194403_g) + { +@@ -242,11 +315,14 @@ + } + + this.serverController.profiler.endSection(); +- +- if (this.chatSpamThresholdCount > 0) ++ // CraftBukkit start ++ for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ; ++ /* Use thread-safe field access instead ++ if (this.chatThrottle > 0) + { +- --this.chatSpamThresholdCount; ++ --this.chatThrottle; + } ++ // CraftBukkit end */ + + if (this.itemDropThreshold > 0) + { +@@ -255,11 +331,12 @@ + + if (this.player.getLastActiveTime() > 0L && this.serverController.getMaxPlayerIdleMinutes() > 0 && MinecraftServer.getCurrentTimeMillis() - this.player.getLastActiveTime() > (long)(this.serverController.getMaxPlayerIdleMinutes() * 1000 * 60)) + { ++ this.player.markPlayerActive(); // CraftBukkit - SPIGOT-854 + this.disconnect(new TextComponentTranslation("multiplayer.disconnect.idling", new Object[0])); + } + } + +- private void captureCurrentPosition() ++ public void captureCurrentPosition() + { + this.firstGoodX = this.player.posX; + this.firstGoodY = this.player.posY; +@@ -274,23 +351,48 @@ + return this.netManager; + } + +- public void disconnect(final ITextComponent textComponent) +- { +- this.netManager.sendPacket(new SPacketDisconnect(textComponent), new GenericFutureListener < Future > () +- { +- public void operationComplete(Future p_operationComplete_1_) throws Exception +- { +- NetHandlerPlayServer.this.netManager.closeChannel(textComponent); ++ @Deprecated ++ public void disconnect(ITextComponent textComponent) { ++ disconnect(CraftChatMessage.fromComponent(textComponent, TextFormatting.WHITE)); ++ } ++ ++ public void disconnect(String s) { ++ // CraftBukkit start - fire PlayerKickEvent ++ if (this.processedDisconnect) { ++ return; ++ } ++ String leaveMessage = TextFormatting.YELLOW + this.player.getName() + " left the game."; ++ ++ PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), s, leaveMessage); ++ ++ if (this.server.getServer().isServerRunning()) { ++ this.server.getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // Do not kick the player ++ return; ++ } ++ // Send the possibly modified leave message ++ s = event.getReason(); ++ // CraftBukkit end ++ final ITextComponent chatcomponenttext = new TextComponentTranslation(s); ++ ++ this.netManager.sendPacket(new SPacketDisconnect(chatcomponenttext), new GenericFutureListener() { ++ public void operationComplete(Future future) throws Exception { // CraftBukkit - decompile error ++ NetHandlerPlayServer.this.netManager.closeChannel(chatcomponenttext); + } + }); ++ this.onDisconnect(chatcomponenttext); // CraftBukkit - fire quit instantly + this.netManager.disableAutoRead(); +- Futures.getUnchecked(this.serverController.addScheduledTask(new Runnable() ++ // CraftBukkit - Don't wait ++ this.serverController.addScheduledTask(new Runnable() + { + public void run() + { + NetHandlerPlayServer.this.netManager.checkDisconnected(); + } +- })); ++ }); + } + + public void processInput(CPacketInput packetIn) +@@ -345,9 +447,34 @@ + double d9 = entity.motionX * entity.motionX + entity.motionY * entity.motionY + entity.motionZ * entity.motionZ; + double d10 = d6 * d6 + d7 * d7 + d8 * d8; + +- if (d10 - d9 > 100.0D && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(entity.getName()))) +- { +- LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", entity.getName(), this.player.getName(), Double.valueOf(d6), Double.valueOf(d7), Double.valueOf(d8)); ++ //if (d10 - d9 > 100.0D && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(entity.getName()))){ ++ // CraftBukkit start - handle custom speeds and skipped ticks ++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); ++ this.lastTick = (int) (System.currentTimeMillis() / 50); ++ ++ ++this.movePacketCounter; ++ int i = this.movePacketCounter - this.lastMovePacketCounter; ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { ++ NetHandlerPlayServer.LOGGER.debug(this.player.getName() + " is sending move packets too frequently (" + i + " packets since last tick)"); ++ i = 1; ++ } ++ ++ if (d10 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ float speed; ++ if (player.capabilities.isFlying) { ++ speed = player.capabilities.flySpeed * 20f; ++ } else { ++ speed = player.capabilities.walkSpeed * 10f; ++ } ++ speed *= 2f; // TODO: Get the speed of the vehicle instead of the player ++ ++ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(entity.getName()))) { // Spigot ++ // CraftBukkit end + this.netManager.sendPacket(new SPacketMoveVehicle(entity)); + return; + } +@@ -370,10 +497,9 @@ + d10 = d6 * d6 + d7 * d7 + d8 * d8; + boolean flag1 = false; + +- if (d10 > 0.0625D) ++ if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) // Spigot + { + flag1 = true; +- LOGGER.warn("{} moved wrongly!", (Object)entity.getName()); + } + + entity.setPositionAndRotation(d3, d4, d5, f, f1); +@@ -388,6 +514,62 @@ + return; + } + ++ // CraftBukkit start - fire PlayerMoveEvent ++ Player player = this.getPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ to.setX(packetIn.getX()); ++ to.setY(packetIn.getY()); ++ to.setZ(packetIn.getZ()); ++ ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ to.setYaw(packetIn.getYaw()); ++ to.setPitch(packetIn.getPitch()); ++ ++ // Prevent 40 event-calls for less than a single pixel of movement >.> ++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2); ++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch()); ++ ++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isMovementBlocked()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ // Skip the first time we do this ++ if (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.server.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ return; ++ } ++ ++ // If a Plugin has changed the To destination then we teleport the Player ++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. ++ // We only do this if the Event was not cancelled. ++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ return; ++ } ++ ++ // Check to see if the Players Location has some how changed during the call of the event. ++ // This can happen due to a plugin teleporting the player instead of using .setTo() ++ if (!from.equals(this.getPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end ++ + this.serverController.getPlayerList().serverUpdateMovingPlayer(this.player); + this.player.addMovementStat(this.player.posX - d0, this.player.posY - d1, this.player.posZ - d2); + this.vehicleFloating = d11 >= -0.03125D && !this.serverController.isFlightAllowed() && !worldserver.checkBlockCollision(entity.getEntityBoundingBox().grow(0.0625D).expand(0.0D, -0.55D, 0.0D)); +@@ -402,7 +584,7 @@ + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); + +- if (packetIn.getTeleportId() == this.teleportId) ++ if (packetIn.getTeleportId() == this.teleportId && this.targetPos != null) + { + this.player.setPositionAndRotation(this.targetPos.x, this.targetPos.y, this.targetPos.z, this.player.rotationYaw, this.player.rotationPitch); + +@@ -461,7 +643,7 @@ + { + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + +- if (!this.player.queuedEndExit) ++ if (!this.player.queuedEndExit && !this.player.isMovementBlocked()) + { + if (this.networkTickCount == 0) + { +@@ -475,6 +657,7 @@ + this.lastPositionUpdate = this.networkTickCount; + this.setPlayerLocation(this.targetPos.x, this.targetPos.y, this.targetPos.z, this.player.rotationYaw, this.player.rotationPitch); + } ++ this.allowedPlayerTicks = 20; + } + else + { +@@ -484,9 +667,17 @@ + { + this.player.setPositionAndRotation(this.player.posX, this.player.posY, this.player.posZ, packetIn.getYaw(this.player.rotationYaw), packetIn.getPitch(this.player.rotationPitch)); + this.serverController.getPlayerList().serverUpdateMovingPlayer(this.player); ++ this.allowedPlayerTicks = 20; + } + else + { ++ // CraftBukkit - Make sure the move is valid but then reset it for plugins to modify ++ double prevX = player.posX; ++ double prevY = player.posY; ++ double prevZ = player.posZ; ++ float prevYaw = player.rotationYaw; ++ float prevPitch = player.rotationPitch; ++ // CraftBukkit end + double d0 = this.player.posX; + double d1 = this.player.posY; + double d2 = this.player.posZ; +@@ -514,19 +705,34 @@ + ++this.movePacketCounter; + int i = this.movePacketCounter - this.lastMovePacketCounter; + +- if (i > 5) +- { ++ // CraftBukkit start - handle custom speeds and skipped ticks ++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); ++ this.lastTick = (int) (System.currentTimeMillis() / 50); ++ ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { + LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName(), Integer.valueOf(i)); + i = 1; + } + ++ if (packetIn.rotating || d11 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ float speed; ++ if (player.capabilities.isFlying) { ++ speed = player.capabilities.flySpeed * 20f; ++ } else { ++ speed = player.capabilities.walkSpeed * 10f; ++ } ++ + if (!this.player.isInvulnerableDimensionChange() && (!this.player.getServerWorld().getGameRules().getBoolean("disableElytraMovementCheck") || !this.player.isElytraFlying())) + { + float f2 = this.player.isElytraFlying() ? 300.0F : 100.0F; + +- if (d11 - d10 > (double)(f2 * (float)i) && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(this.player.getName()))) ++ if (d11 - d10 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(this.player.getName()))) // Spigot + { +- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName(), Double.valueOf(d7), Double.valueOf(d8), Double.valueOf(d9)); + this.setPlayerLocation(this.player.posX, this.player.posY, this.player.posZ, this.player.rotationYaw, this.player.rotationPitch); + return; + } +@@ -557,10 +763,9 @@ + d11 = d7 * d7 + d8 * d8 + d9 * d9; + boolean flag = false; + +- if (!this.player.isInvulnerableDimensionChange() && d11 > 0.0625D && !this.player.isPlayerSleeping() && !this.player.interactionManager.isCreative() && this.player.interactionManager.getGameType() != GameType.SPECTATOR) ++ if (!this.player.isInvulnerableDimensionChange() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isPlayerSleeping() && !this.player.interactionManager.isCreative() && this.player.interactionManager.getGameType() != GameType.SPECTATOR) // Spigot + { + flag = true; +- LOGGER.warn("{} moved wrongly!", (Object)this.player.getName()); + } + + this.player.setPositionAndRotation(d4, d5, d6, f, f1); +@@ -577,6 +782,69 @@ + } + } + ++ // CraftBukkit start - fire PlayerMoveEvent ++ // Rest to old location first ++ this.player.setPositionAndRotation(prevX, prevY, prevZ, prevYaw, prevPitch); ++ ++ Player player = this.getPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ if (packetIn.moving) { ++ to.setX(packetIn.x); ++ to.setY(packetIn.y); ++ to.setZ(packetIn.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packetIn.rotating) { ++ to.setYaw(packetIn.yaw); ++ to.setPitch(packetIn.pitch); ++ } ++ ++ // Prevent 40 event-calls for less than a single pixel of movement >.> ++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2); ++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch()); ++ ++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isMovementBlocked()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ // Skip the first time we do this ++ if (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.server.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ return; ++ } ++ ++ // If a Plugin has changed the To destination then we teleport the Player ++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. ++ // We only do this if the Event was not cancelled. ++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ return; ++ } ++ ++ // Check to see if the Players Location has some how changed during the call of the event. ++ // This can happen due to a plugin teleporting the player instead of using .setTo() ++ if (!from.equals(this.getPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ this.player.setPositionAndRotation(d4, d5, d6, f, f1); // Copied from above ++ // CraftBukkit end ++ + this.floating = d12 >= -0.03125D; + this.floating &= !this.serverController.isFlightAllowed() && !this.player.capabilities.allowFlying; + this.floating &= !this.player.isPotionActive(MobEffects.LEVITATION) && !this.player.isElytraFlying() && !worldserver.checkBlockCollision(this.player.getEntityBoundingBox().grow(0.0625D).expand(0.0D, -0.55D, 0.0D)); +@@ -598,8 +866,74 @@ + this.setPlayerLocation(x, y, z, yaw, pitch, Collections.emptySet()); + } + ++ // CraftBukkit start - Delegate to teleport(Location) ++ public void setPlayerLocation(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { ++ this.setPlayerLocation(d0, d1, d2, f, f1, Collections.emptySet(), cause); ++ } ++ + public void setPlayerLocation(double x, double y, double z, float yaw, float pitch, Set relativeSet) + { ++ this.setPlayerLocation(x, y, z, yaw, pitch, relativeSet, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public void setPlayerLocation(double d0, double d1, double d2, float f, float f1, Set set, PlayerTeleportEvent.TeleportCause cause) { ++ Player player = this.getPlayer(); ++ Location from = player.getLocation(); ++ ++ double x = d0; ++ double y = d1; ++ double z = d2; ++ float yaw = f; ++ float pitch = f1; ++ if (set.contains(SPacketPlayerPosLook.EnumFlags.X)) { ++ x += from.getX(); ++ } ++ if (set.contains(SPacketPlayerPosLook.EnumFlags.Y)) { ++ y += from.getY(); ++ } ++ if (set.contains(SPacketPlayerPosLook.EnumFlags.Z)) { ++ z += from.getZ(); ++ } ++ if (set.contains(SPacketPlayerPosLook.EnumFlags.Y_ROT)) { ++ yaw += from.getYaw(); ++ } ++ if (set.contains(SPacketPlayerPosLook.EnumFlags.X_ROT)) { ++ pitch += from.getPitch(); ++ } ++ ++ ++ Location to = new Location(this.getPlayer().getWorld(), x, y, z, yaw, pitch); ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || !to.equals(event.getTo())) { ++ set.clear(); // Can't relative teleport ++ to = event.isCancelled() ? event.getFrom() : event.getTo(); ++ d0 = to.getX(); ++ d1 = to.getY(); ++ d2 = to.getZ(); ++ f = to.getYaw(); ++ f1 = to.getPitch(); ++ } ++ ++ this.internalTeleport(d0, d1, d2, f, f1, set); ++ } ++ ++ public void teleport(Location dest) { ++ internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); ++ } ++ ++ private void internalTeleport(double x, double y, double z, float yaw, float pitch, Set relativeSet) { ++ // CraftBukkit start ++ if (Float.isNaN(yaw)) { ++ yaw = 0; ++ } ++ if (Float.isNaN(pitch)) { ++ pitch = 0; ++ } ++ ++ this.justTeleported = true; ++ // CraftBukkit end + double d0 = relativeSet.contains(SPacketPlayerPosLook.EnumFlags.X) ? this.player.posX : 0.0D; + double d1 = relativeSet.contains(SPacketPlayerPosLook.EnumFlags.Y) ? this.player.posY : 0.0D; + double d2 = relativeSet.contains(SPacketPlayerPosLook.EnumFlags.Z) ? this.player.posZ : 0.0D; +@@ -617,6 +951,14 @@ + f1 = pitch + this.player.rotationPitch; + } + ++ // CraftBukkit start - update last location ++ this.lastPosX = this.targetPos.x; ++ this.lastPosY = this.targetPos.y; ++ this.lastPosZ = this.targetPos.z; ++ this.lastYaw = f; ++ this.lastPitch = f1; ++ // CraftBukkit end ++ + if (++this.teleportId == Integer.MAX_VALUE) + { + this.teleportId = 0; +@@ -630,6 +972,7 @@ + public void processPlayerDigging(CPacketPlayerDigging packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + BlockPos blockpos = packetIn.getPosition(); + this.player.markPlayerActive(); +@@ -641,7 +984,16 @@ + if (!this.player.isSpectator()) + { + ItemStack itemstack = this.player.getHeldItem(EnumHand.OFF_HAND); +- this.player.setHeldItem(EnumHand.OFF_HAND, this.player.getHeldItem(EnumHand.MAIN_HAND)); ++ // this.player.setHeldItem(EnumHand.OFF_HAND, this.player.getHeldItem(EnumHand.MAIN_HAND)); ++ // CraftBukkit start ++ PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(getPlayer(), CraftItemStack.asBukkitCopy(itemstack), CraftItemStack.asBukkitCopy(this.player.getHeldItem(EnumHand.MAIN_HAND))); ++ this.server.getPluginManager().callEvent(swapItemsEvent); ++ if (swapItemsEvent.isCancelled()) { ++ return; ++ } ++ itemstack = CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem()); ++ this.player.setHeldItem(EnumHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem())); ++ // CraftBukkit end + this.player.setHeldItem(EnumHand.MAIN_HAND, itemstack); + } + +@@ -650,6 +1002,20 @@ + + if (!this.player.isSpectator()) + { ++ // limit how quickly items can be dropped ++ // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick. ++ if (this.lastDropTick != MinecraftServer.currentTick) { ++ this.dropCount = 0; ++ this.lastDropTick = MinecraftServer.currentTick; ++ } else { ++ // Else we increment the drop count and check the amount. ++ this.dropCount++; ++ if (this.dropCount >= 20) { ++ LOGGER.warn(this.player.getName() + " dropped their items too quickly!"); ++ this.disconnect("You dropped your items too quickly (Hacking?)"); ++ return; ++ } ++ } + this.player.dropItem(false); + } + +@@ -694,7 +1060,15 @@ + } + else + { ++ // CraftBukkit start - fire PlayerInteractEvent ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, blockpos, packetIn.getFacing(), this.player.inventory.getCurrentItem(), EnumHand.MAIN_HAND); + this.player.connection.sendPacket(new SPacketBlockChange(worldserver, blockpos)); ++ // Update any tile entity data for this block ++ TileEntity tileentity = worldserver.getTileEntity(blockpos); ++ if (tileentity != null) { ++ this.player.connection.sendPacket(tileentity.getUpdatePacket()); ++ } ++ // CraftBukkit end + } + } + else +@@ -725,6 +1099,7 @@ + public void processTryUseItemOnBlock(CPacketPlayerTryUseItemOnBlock packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + EnumHand enumhand = packetIn.getHand(); + ItemStack itemstack = this.player.getHeldItem(enumhand); +@@ -738,6 +1113,13 @@ + dist *= dist; + if (this.targetPos == null && this.player.getDistanceSq((double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 0.5D, (double)blockpos.getZ() + 0.5D) < dist && !this.serverController.isBlockProtected(worldserver, blockpos, this.player) && worldserver.getWorldBorder().contains(blockpos)) + { ++ // CraftBukkit start - Check if we can actually do something over this large a distance ++ Location eyeLoc = this.getPlayer().getEyeLocation(); ++ double reachDistance = NumberConversions.square(eyeLoc.getX() - blockpos.getX()) + NumberConversions.square(eyeLoc.getY() - blockpos.getY()) + NumberConversions.square(eyeLoc.getZ() - blockpos.getZ()); ++ if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) { ++ return; ++ } ++ // CraftBukkit end + this.player.interactionManager.processRightClickBlock(this.player, worldserver, itemstack, enumhand, blockpos, enumfacing, packetIn.getFacingX(), packetIn.getFacingY(), packetIn.getFacingZ()); + } + } +@@ -755,6 +1137,7 @@ + public void processTryUseItem(CPacketPlayerTryUseItem packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + EnumHand enumhand = packetIn.getHand(); + ItemStack itemstack = this.player.getHeldItem(enumhand); +@@ -762,7 +1145,46 @@ + + if (!itemstack.isEmpty()) + { +- this.player.interactionManager.processRightClick(this.player, worldserver, itemstack, enumhand); ++ // this.player.interactionManager.processRightClick(this.player, worldserver, itemstack, enumhand); ++ // CraftBukkit start ++ // Raytrace to look for 'rogue armswings' ++ float f1 = this.player.rotationPitch; ++ float f2 = this.player.rotationYaw; ++ double d0 = this.player.posX; ++ double d1 = this.player.posY + (double) this.player.getEyeHeight(); ++ double d2 = this.player.posZ; ++ Vec3d vec3d = new Vec3d(d0, d1, d2); ++ ++ float f3 = MathHelper.cos(-f2 * 0.017453292F - 3.1415927F); ++ float f4 = MathHelper.sin(-f2 * 0.017453292F - 3.1415927F); ++ float f5 = -MathHelper.cos(-f1 * 0.017453292F); ++ float f6 = MathHelper.sin(-f1 * 0.017453292F); ++ float f7 = f4 * f5; ++ float f8 = f3 * f5; ++ double d3 = player.interactionManager.getGameType()== GameType.CREATIVE ? 5.0D : 4.5D; ++ Vec3d vec3d1 = vec3d.addVector((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); ++ RayTraceResult movingobjectposition = this.player.world.rayTraceBlocks(vec3d, vec3d1, false); ++ ++ boolean cancelled; ++ if (movingobjectposition == null || movingobjectposition.typeOfHit != RayTraceResult.Type.BLOCK) { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } else { ++ if (player.interactionManager.firedInteract) { ++ player.interactionManager.firedInteract = false; ++ cancelled = player.interactionManager.interactResult; ++ } else { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, movingobjectposition.getBlockPos(), movingobjectposition.sideHit, itemstack, true, enumhand); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } ++ } ++ ++ if (cancelled) { ++ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 ++ } else { ++ this.player.interactionManager.processRightClick(this.player, worldserver, itemstack, enumhand); ++ } ++ // CraftBukkit end + } + } + +@@ -774,7 +1196,8 @@ + { + Entity entity = null; + +- for (WorldServer worldserver : this.serverController.worlds) ++ // CraftBukkit - use the worlds array list ++ for (WorldServer worldserver : this.serverController.worldServerList) + { + if (worldserver != null) + { +@@ -792,6 +1215,7 @@ + this.player.setSpectatingEntity(this.player); + this.player.dismountRidingEntity(); + ++ /* CraftBukkit start - replace with bukkit handling for multi-world + if (entity.world == this.player.world) + { + this.player.setPositionAndUpdate(entity.posX, entity.posY, entity.posZ); +@@ -823,12 +1247,16 @@ + this.serverController.getPlayerList().syncPlayerInventory(this.player); + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerChangedDimensionEvent(this.player, prevDimension, this.player.dimension); + } ++ */ ++ this.player.getBukkitEntity().teleport(entity.getBukkitEntity(), PlayerTeleportEvent.TeleportCause.SPECTATE); + } + } + } + + public void handleResourcePackStatus(CPacketResourcePackStatus packetIn) + { ++ PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), PlayerResourcePackStatusEvent.Status.values()[packetIn.action.ordinal()])); + } + + public void processSteerBoat(CPacketSteerBoat packetIn) +@@ -844,13 +1272,27 @@ + + public void onDisconnect(ITextComponent reason) + { ++ // CraftBukkit start - Rarely it would send a disconnect line twice ++ if (this.processedDisconnect) { ++ return; ++ } else { ++ this.processedDisconnect = true; ++ } ++ // CraftBukkit end + LOGGER.info("{} lost connection: {}", this.player.getName(), reason.getUnformattedText()); +- this.serverController.refreshStatusNextTick(); ++ // CraftBukkit start - Replace vanilla quit message handling with our own. ++ /* + TextComponentTranslation textcomponenttranslation = new TextComponentTranslation("multiplayer.player.left", new Object[] {this.player.getDisplayName()}); + textcomponenttranslation.getStyle().setColor(TextFormatting.YELLOW); + this.serverController.getPlayerList().sendMessage(textcomponenttranslation); ++ */ ++ this.serverController.refreshStatusNextTick(); + this.player.mountEntityAndWakeUp(); +- this.serverController.getPlayerList().playerLoggedOut(this.player); ++ // this.serverController.getPlayerList().playerLoggedOut(this.player); ++ String quitMessage = this.serverController.getPlayerList().playerLoggedOut(this.player); ++ if ((quitMessage != null) && (quitMessage.length() > 0)) { ++ this.serverController.getPlayerList().sendMessage(CraftChatMessage.fromString(quitMessage)); ++ } + + if (this.serverController.isSinglePlayer() && this.player.getName().equals(this.serverController.getServerOwner())) + { +@@ -877,6 +1319,13 @@ + } + } + ++ if (packetIn == null) { ++ return; ++ } else if (packetIn instanceof SPacketSpawnPosition) { ++ SPacketSpawnPosition packet6 = (SPacketSpawnPosition) packetIn; ++ this.player.compassTarget = new Location(this.getPlayer().getWorld(), packet6.spawnBlockPos.getX(), packet6.spawnBlockPos.getY(), packet6.spawnBlockPos.getZ()); ++ } ++ + try + { + this.netManager.sendPacket(packetIn); +@@ -899,23 +1348,42 @@ + public void processHeldItemChange(CPacketHeldItemChange packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); +- ++ if (this.player.isMovementBlocked()) return; + if (packetIn.getSlotId() >= 0 && packetIn.getSlotId() < InventoryPlayer.getHotbarSize()) + { ++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getPlayer(), this.player.inventory.currentItem, packetIn.getSlotId()); ++ this.server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.sendPacket(new SPacketHeldItemChange(this.player.inventory.currentItem)); ++ this.player.markPlayerActive(); ++ return; ++ } + this.player.inventory.currentItem = packetIn.getSlotId(); + this.player.markPlayerActive(); + } + else + { + LOGGER.warn("{} tried to set an invalid carried item", (Object)this.player.getName()); ++ this.disconnect("Nope!"); // CraftBukkit + } + } + + public void processChatMessage(CPacketChatMessage packetIn) + { +- PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ // PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ // CraftBukkit start - async chat ++ // SPIGOT-3638 ++ if (this.serverController.isServerStopped()) { ++ return; ++ } + +- if (this.player.getChatVisibility() == EntityPlayer.EnumChatVisibility.HIDDEN) ++ boolean isSync = packetIn.getMessage().startsWith("/"); ++ if (packetIn.getMessage().startsWith("/")) { ++ PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ } ++ // CraftBukkit end ++ ++ if (this.player.isDead || this.player.getChatVisibility() == EntityPlayer.EnumChatVisibility.HIDDEN) // CraftBukkit - dead men tell no tales + { + TextComponentTranslation textcomponenttranslation = new TextComponentTranslation("chat.cannotSend", new Object[0]); + textcomponenttranslation.getStyle().setColor(TextFormatting.RED); +@@ -931,14 +1399,54 @@ + { + if (!ChatAllowedCharacters.isAllowedCharacter(s.charAt(i))) + { +- this.disconnect(new TextComponentTranslation("multiplayer.disconnect.illegal_characters", new Object[0])); ++ // this.disconnect(new TextComponentTranslation("multiplayer.disconnect.illegal_characters", new Object[0])); ++ // CraftBukkit start - threadsafety ++ if (!isSync) { ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ NetHandlerPlayServer.this.disconnect(new TextComponentTranslation("multiplayer.disconnect.illegal_characters", new Object[0])); ++ return null; ++ } ++ }; ++ ++ this.serverController.processQueue.add(waitable); ++ ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); ++ } catch (ExecutionException e) { ++ throw new RuntimeException(e); ++ } ++ } else { ++ this.disconnect(new TextComponentTranslation("multiplayer.disconnect.illegal_characters", new Object[0])); ++ } ++ // CraftBukkit end + return; + } + } + +- if (s.startsWith("/")) +- { +- this.handleSlashCommand(s); ++ // CraftBukkit start ++ if (isSync) { ++ try { ++ this.serverController.server.playerCommandState = true; ++ this.handleSlashCommand(s); ++ } finally { ++ this.serverController.server.playerCommandState = false; ++ } ++ } else if (s.isEmpty()) { ++ LOGGER.warn(this.player.getName() + " tried to send an empty message"); ++ } else if (getPlayer().isConversing()) { ++ getPlayer().acceptConversationInput(s); ++ } else if (this.player.getChatVisibility() == EntityPlayer.EnumChatVisibility.SYSTEM) { // Re-add "Command Only" flag check ++ TextComponentTranslation chatmessage = new TextComponentTranslation("chat.cannotSend", new Object[0]); ++ ++ chatmessage.getStyle().setColor(TextFormatting.RED); ++ this.sendPacket(new SPacketChat(chatmessage)); ++ } else if (true) { ++ this.chat(s, true); ++ // CraftBukkit end - the below is for reference. :) + } + else + { +@@ -948,30 +1456,205 @@ + this.serverController.getPlayerList().sendMessage(itextcomponent, false); + } + +- this.chatSpamThresholdCount += 20; +- +- if (this.chatSpamThresholdCount > 200 && !this.serverController.getPlayerList().canSendCommands(this.player.getGameProfile())) ++ // Spigot start - spam exclusions ++ boolean counted = true; ++ for (String exclude : org.spigotmc.SpigotConfig.spamExclusions) + { +- this.disconnect(new TextComponentTranslation("disconnect.spam", new Object[0])); ++ if (exclude != null && s.startsWith(exclude)) ++ { ++ counted = false; ++ break; ++ } + } ++ // Spigot end ++ // CraftBukkit start - replaced with thread safe throttle ++ // this.chatThrottle += 20; ++ if (counted && chatSpamField.addAndGet(this, 20) > 200 && !this.serverController.getPlayerList().canSendCommands(this.player.getGameProfile())) { ++ if (!isSync) { ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ NetHandlerPlayServer.this.disconnect(new TextComponentTranslation("disconnect.spam", new Object[0])); ++ return null; ++ } ++ }; ++ ++ this.serverController.processQueue.add(waitable); ++ ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); ++ } catch (ExecutionException e) { ++ throw new RuntimeException(e); ++ } ++ } else { ++ this.disconnect(new TextComponentTranslation("disconnect.spam", new Object[0])); ++ } ++ // CraftBukkit end ++ } + } + } + ++ public void chat(String s, boolean async) { ++ if (s.isEmpty() || this.player.getChatVisibility() == EntityPlayer.EnumChatVisibility.HIDDEN) { ++ return; ++ } ++ ++ if (!async && s.startsWith("/")) { ++ this.handleSlashCommand(s); ++ } else if (this.player.getChatVisibility() == EntityPlayer.EnumChatVisibility.SYSTEM) { ++ // Do nothing, this is coming from a plugin ++ } else { ++ Player player = this.getPlayer(); ++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(serverController)); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { ++ // Evil plugins still listening to deprecated event ++ final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients()); ++ queueEvent.setCancelled(event.isCancelled()); ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent); ++ ++ if (queueEvent.isCancelled()) { ++ return null; ++ } ++ ++ String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage()); ++ NetHandlerPlayServer.this.serverController.console.sendMessage(message); ++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) { ++ for (Object player : NetHandlerPlayServer.this.serverController.getPlayerList().getPlayers()) { ++ ((EntityPlayerMP) player).sendMessage(CraftChatMessage.fromString(message)); ++ } ++ } else { ++ for (Player player : queueEvent.getRecipients()) { ++ player.sendMessage(message); ++ } ++ } ++ return null; ++ }}; ++ if (async) { ++ serverController.processQueue.add(waitable); ++ } else { ++ waitable.run(); ++ } ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (ExecutionException e) { ++ throw new RuntimeException("Exception processing chat event", e.getCause()); ++ } ++ } else { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); ++ serverController.console.sendMessage(s); ++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) { ++ for (Object recipient : serverController.getPlayerList().getPlayers()) { ++ ((EntityPlayerMP) recipient).sendMessage(CraftChatMessage.fromString(s)); ++ } ++ } else { ++ for (Player recipient : event.getRecipients()) { ++ recipient.sendMessage(s); ++ } ++ } ++ } ++ } ++ } ++ + private void handleSlashCommand(String command) + { +- this.serverController.getCommandManager().executeCommand(this.player, command); ++ // CraftBukkit start - whole method ++ if (org.spigotmc.SpigotConfig.logCommands) // Spigot ++ this.LOGGER.info(this.player.getName() + " issued server command: " + command); ++ ++ CraftPlayer player = this.getPlayer(); ++ ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, command, new LazyPlayerSet(serverController)); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ try { ++ if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { ++ return; ++ } ++ } catch (org.bukkit.command.CommandException ex) { ++ player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); ++ java.util.logging.Logger.getLogger(NetHandlerPlayServer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); ++ return; ++ } ++ // CraftBukkit end + } + + public void handleAnimation(CPacketAnimation packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + this.player.markPlayerActive(); ++ // CraftBukkit start - Raytrace to look for 'rogue armswings' ++ float f1 = this.player.rotationPitch; ++ float f2 = this.player.rotationYaw; ++ double d0 = this.player.posX; ++ double d1 = this.player.posY + (double) this.player.getEyeHeight(); ++ double d2 = this.player.posZ; ++ Vec3d vec3d = new Vec3d(d0, d1, d2); ++ ++ float f3 = MathHelper.cos(-f2 * 0.017453292F - 3.1415927F); ++ float f4 = MathHelper.sin(-f2 * 0.017453292F - 3.1415927F); ++ float f5 = -MathHelper.cos(-f1 * 0.017453292F); ++ float f6 = MathHelper.sin(-f1 * 0.017453292F); ++ float f7 = f4 * f5; ++ float f8 = f3 * f5; ++ double d3 = player.interactionManager.getGameType()== GameType.CREATIVE ? 5.0D : 4.5D; ++ Vec3d vec3d1 = vec3d.addVector((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); ++ RayTraceResult movingobjectposition = this.player.world.rayTraceBlocks(vec3d, vec3d1, false); ++ ++ if (movingobjectposition == null || movingobjectposition.typeOfHit != RayTraceResult.Type.BLOCK) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.inventory.getCurrentItem(), EnumHand.MAIN_HAND); ++ } ++ ++ // Arm swing animation ++ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getPlayer()); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end + this.player.swingArm(packetIn.getHand()); + } + + public void processEntityAction(CPacketEntityAction packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isDead) return; ++ switch (packetIn.getAction()) { ++ case START_SNEAKING: ++ case STOP_SNEAKING: ++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getPlayer(), packetIn.getAction() == CPacketEntityAction.Action.START_SNEAKING); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ break; ++ case START_SPRINTING: ++ case STOP_SPRINTING: ++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getPlayer(), packetIn.getAction() == CPacketEntityAction.Action.START_SPRINTING); ++ this.server.getPluginManager().callEvent(e2); ++ ++ if (e2.isCancelled()) { ++ return; ++ } ++ break; ++ } + this.player.markPlayerActive(); + + switch (packetIn.getAction()) +@@ -1053,6 +1736,7 @@ + public void processUseEntity(CPacketUseEntity packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + Entity entity = packetIn.getEntityFromWorld(worldserver); + this.player.markPlayerActive(); +@@ -1069,20 +1753,58 @@ + + if (this.player.getDistanceSq(entity) < d0) + { ++ ItemStack itemInHand = this.player.getHeldItem(packetIn.getHand() == null ? EnumHand.MAIN_HAND : packetIn.getHand()); // CraftBukkit ++ ++ if (packetIn.getAction() == CPacketUseEntity.Action.INTERACT ++ || packetIn.getAction() == CPacketUseEntity.Action.INTERACT_AT) { ++ // CraftBukkit start ++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof EntityLiving; ++ Item origItem = this.player.inventory.getCurrentItem() == null ? null : this.player.inventory.getCurrentItem().getItem(); ++ PlayerInteractEntityEvent event; ++ if (packetIn.getAction() == CPacketUseEntity.Action.INTERACT) { ++ event = new PlayerInteractEntityEvent((Player) this.getPlayer(), entity.getBukkitEntity(), (packetIn.getHand() == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); ++ } else { ++ Vec3d target = packetIn.getHitVec(); ++ event = new PlayerInteractAtEntityEvent((Player) this.getPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(target.x, target.y, target.z), (packetIn.getHand() == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); ++ } ++ this.server.getPluginManager().callEvent(event); ++ ++ if (triggerLeashUpdate && (event.isCancelled() || this.player.inventory.getCurrentItem() == null || this.player.inventory.getCurrentItem().getItem() != Items.LEAD)) { ++ // Refresh the current leash state ++ this.sendPacket(new SPacketEntityAttach(entity, ((EntityLiving) entity).getLeashHolder())); ++ } ++ ++ if (event.isCancelled() || this.player.inventory.getCurrentItem() == null || this.player.inventory.getCurrentItem().getItem() != origItem) { ++ // Refresh the current entity metadata ++ this.sendPacket(new SPacketEntityMetadata(entity.getEntityId(), entity.getDataManager(), true)); ++ } ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ } ++ + if (packetIn.getAction() == CPacketUseEntity.Action.INTERACT) + { + EnumHand enumhand = packetIn.getHand(); + this.player.interactOn(entity, enumhand); ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ this.player.sendContainerToPlayer(this.player.openContainer); ++ } + } + else if (packetIn.getAction() == CPacketUseEntity.Action.INTERACT_AT) + { + EnumHand enumhand1 = packetIn.getHand(); + if(net.minecraftforge.common.ForgeHooks.onInteractEntityAt(player, entity, packetIn.getHitVec(), enumhand1) != null) return; + entity.applyPlayerInteraction(this.player, packetIn.getHitVec(), enumhand1); ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ this.player.sendContainerToPlayer(this.player.openContainer); ++ } + } + else if (packetIn.getAction() == CPacketUseEntity.Action.ATTACK) + { +- if (entity instanceof EntityItem || entity instanceof EntityXPOrb || entity instanceof EntityArrow || entity == this.player) ++ if (entity instanceof EntityItem || entity instanceof EntityXPOrb || entity instanceof EntityArrow || (entity == this.player && !player.isSpectator())) + { + this.disconnect(new TextComponentTranslation("multiplayer.disconnect.invalid_entity_attacked", new Object[0])); + this.serverController.logWarning("Player " + this.player.getName() + " tried to attack an invalid entity"); +@@ -1090,6 +1812,10 @@ + } + + this.player.attackTargetEntityWithCurrentItem(entity); ++ ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ this.player.sendContainerToPlayer(this.player.openContainer); ++ } + } + } + } +@@ -1108,7 +1834,8 @@ + if (this.player.queuedEndExit) + { + this.player.queuedEndExit = false; +- this.player = this.serverController.getPlayerList().recreatePlayerEntity(this.player, 0, true); ++ // this.player = this.serverController.getPlayerList().recreatePlayerEntity(this.player, 0, true); ++ this.serverController.getPlayerList().changeDimension(this.player, 0, PlayerTeleportEvent.TeleportCause.END_PORTAL); // CraftBukkit - reroute logic through custom portal management + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, DimensionType.THE_END, DimensionType.OVERWORLD); + } + else +@@ -1136,17 +1863,21 @@ + public void processCloseWindow(CPacketCloseWindow packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; ++ CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit + this.player.closeContainer(); + } + + public void processClickWindow(CPacketClickWindow packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + this.player.markPlayerActive(); + +- if (this.player.openContainer.windowId == packetIn.getWindowId() && this.player.openContainer.getCanCraft(this.player)) ++ if (this.player.openContainer.windowId == packetIn.getWindowId() && this.player.openContainer.getCanCraft(this.player) && this.player.openContainer.canInteractWith(this.player)) + { +- if (this.player.isSpectator()) ++ boolean cancelled = this.player.isSpectator(); // CraftBukkit - see below if ++ if (false/*this.player.isSpectator()*/) + { + NonNullList nonnulllist = NonNullList.create(); + +@@ -1159,9 +1890,286 @@ + } + else + { +- ItemStack itemstack2 = this.player.openContainer.slotClick(packetIn.getSlotId(), packetIn.getUsedButton(), packetIn.getClickType(), this.player); ++ // ItemStack itemstack2 = this.player.openContainer.slotClick(packetIn.getSlotId(), packetIn.getUsedButton(), packetIn.getClickType(), this.player); + +- if (ItemStack.areItemStacksEqualUsingNBTShareTag(packetIn.getClickedItem(), itemstack2)) ++ // CraftBukkit start - Call InventoryClickEvent ++ if (packetIn.getSlotId() < -1 && packetIn.getSlotId() != -999) { ++ return; ++ } ++ ++ InventoryView inventory = this.player.openContainer.getBukkitView(); ++ ++ // Kettle start ++ if(inventory == null){ ++ inventory = new CraftInventoryView(this.player.getBukkitEntity(), MinecraftServer.getServerInstance().server.createInventory(this.player.getBukkitEntity(), InventoryType.CHEST), this.player.openContainer); ++ this.player.openContainer.setBukkitView(inventory); ++ } ++ // Kettle end ++ ++ InventoryType.SlotType type = CraftInventoryView.getSlotType(inventory, packetIn.getSlotId()); ++ ++ InventoryClickEvent event; ++ ClickType click = ClickType.UNKNOWN; ++ InventoryAction action = InventoryAction.UNKNOWN; ++ ++ ItemStack itemstack = ItemStack.EMPTY; ++ ++ switch (packetIn.getClickType()) { ++ case PICKUP: ++ if (packetIn.getUsedButton() == 0) { ++ click = ClickType.LEFT; ++ } else if (packetIn.getUsedButton() == 1) { ++ click = ClickType.RIGHT; ++ } ++ if (packetIn.getUsedButton() == 0 || packetIn.getUsedButton() == 1) { ++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves ++ if (packetIn.getSlotId() == -999) { ++ if (!player.inventory.getItemStack().isEmpty()) { ++ action = packetIn.getUsedButton() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; ++ } ++ } else if (packetIn.getSlotId() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (slot != null) { ++ ItemStack clickedItem = slot.getStack(); ++ ItemStack cursor = player.inventory.getItemStack(); ++ if (clickedItem.isEmpty()) { ++ if (!cursor.isEmpty()) { ++ action = packetIn.getUsedButton() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE; ++ } ++ } else if (slot.canTakeStack(player)) { ++ if (cursor.isEmpty()) { ++ action = packetIn.getUsedButton() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF; ++ } else if (slot.isItemValid(cursor)) { ++ if (clickedItem.isItemEqual(cursor) && ItemStack.areItemStackTagsEqual(clickedItem, cursor)) { ++ int toPlace = packetIn.getUsedButton() == 0 ? cursor.getCount() : 1; ++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount()); ++ toPlace = Math.min(toPlace, slot.inventory.getInventoryStackLimit() - clickedItem.getCount()); ++ if (toPlace == 1) { ++ action = InventoryAction.PLACE_ONE; ++ } else if (toPlace == cursor.getCount()) { ++ action = InventoryAction.PLACE_ALL; ++ } else if (toPlace < 0) { ++ action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks ++ } else if (toPlace != 0) { ++ action = InventoryAction.PLACE_SOME; ++ } ++ } else if (cursor.getCount() <= slot.getSlotStackLimit()) { ++ action = InventoryAction.SWAP_WITH_CURSOR; ++ } ++ } else if (cursor.getItem() == clickedItem.getItem() && (!cursor.getHasSubtypes() || cursor.getMetadata() == clickedItem.getMetadata()) && ItemStack.areItemStackTagsEqual(cursor, clickedItem)) { ++ if (clickedItem.getCount() >= 0) { ++ if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) { ++ // As of 1.5, this is result slots only ++ action = InventoryAction.PICKUP_ALL; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ break; ++ // TODO check on updates ++ case QUICK_MOVE: ++ if (packetIn.getUsedButton() == 0) { ++ click = ClickType.SHIFT_LEFT; ++ } else if (packetIn.getUsedButton() == 1) { ++ click = ClickType.SHIFT_RIGHT; ++ } ++ if (packetIn.getUsedButton() == 0 || packetIn.getUsedButton() == 1) { ++ if (packetIn.getSlotId() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (slot != null && slot.canTakeStack(this.player) && slot.getHasStack()) { ++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } ++ break; ++ case SWAP: ++ if (packetIn.getUsedButton() >= 0 && packetIn.getUsedButton() < 9) { ++ click = ClickType.NUMBER_KEY; ++ Slot clickedSlot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (clickedSlot.canTakeStack(player)) { ++ ItemStack hotbar = this.player.inventory.getStackInSlot(packetIn.getUsedButton()); ++ boolean canCleanSwap = hotbar.isEmpty() || (clickedSlot.inventory == player.inventory && clickedSlot.isItemValid(hotbar)); // the slot will accept the hotbar item ++ if (clickedSlot.getHasStack()) { ++ if (canCleanSwap) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.HOTBAR_MOVE_AND_READD; ++ } ++ } else if (!clickedSlot.getHasStack() && !hotbar.isEmpty() && clickedSlot.isItemValid(hotbar)) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ break; ++ case CLONE: ++ if (packetIn.getUsedButton() == 2) { ++ click = ClickType.MIDDLE; ++ if (packetIn.getSlotId() < 0) { // Paper - GH-404 ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (slot != null && slot.getHasStack() && player.capabilities.isCreativeMode && player.inventory.getItemStack().isEmpty()) { ++ action = InventoryAction.CLONE_STACK; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } else { ++ click = ClickType.UNKNOWN; ++ action = InventoryAction.UNKNOWN; ++ } ++ break; ++ case THROW: ++ if (packetIn.getSlotId() >= 0) { ++ if (packetIn.getUsedButton() == 0) { ++ click = ClickType.DROP; ++ Slot slot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (slot != null && slot.getHasStack() && slot.canTakeStack(player) && !slot.getStack().isEmpty() && slot.getStack().getItem() != Item.getItemFromBlock(Blocks.AIR)) { ++ action = InventoryAction.DROP_ONE_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else if (packetIn.getUsedButton() == 1) { ++ click = ClickType.CONTROL_DROP; ++ Slot slot = this.player.openContainer.getSlot(packetIn.getSlotId()); ++ if (slot != null && slot.getHasStack() && slot.canTakeStack(player) && !slot.getStack().isEmpty() && slot.getStack().getItem() != Item.getItemFromBlock(Blocks.AIR)) { ++ action = InventoryAction.DROP_ALL_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } else { ++ // Sane default (because this happens when they are holding nothing. Don't ask why.) ++ click = ClickType.LEFT; ++ if (packetIn.getUsedButton() == 1) { ++ click = ClickType.RIGHT; ++ } ++ action = InventoryAction.NOTHING; ++ } ++ break; ++ case QUICK_CRAFT: ++ itemstack = this.player.openContainer.slotClick(packetIn.getSlotId(), packetIn.getUsedButton(), packetIn.getClickType(), this.player); ++ break; ++ case PICKUP_ALL: ++ click = ClickType.DOUBLE_CLICK; ++ action = InventoryAction.NOTHING; ++ if (packetIn.getSlotId() >= 0 && !this.player.inventory.getItemStack().isEmpty()) { ++ ItemStack cursor = this.player.inventory.getItemStack(); ++ action = InventoryAction.NOTHING; ++ // Quick check for if we have any of the item ++ if (inventory.getTopInventory().contains(org.bukkit.Material.getMaterial(Item.getIdFromItem(cursor.getItem()))) || inventory.getBottomInventory().contains(org.bukkit.Material.getMaterial(Item.getIdFromItem(cursor.getItem())))) { ++ action = InventoryAction.COLLECT_TO_CURSOR; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (packetIn.getClickType() != net.minecraft.inventory.ClickType.QUICK_CRAFT) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new InventoryClickEvent(inventory, type, packetIn.getSlotId(), click, action, packetIn.getUsedButton()); ++ } else { ++ event = new InventoryClickEvent(inventory, type, packetIn.getSlotId(), click, action); ++ } ++ ++ org.bukkit.inventory.Inventory top = inventory.getTopInventory(); ++ if (packetIn.getSlotId() == 0 && top instanceof CraftingInventory) { ++ org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe(); ++ if (recipe != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new CraftItemEvent(recipe, inventory, type, packetIn.getSlotId(), click, action, packetIn.getUsedButton()); ++ } else { ++ event = new CraftItemEvent(recipe, inventory, type, packetIn.getSlotId(), click, action); ++ } ++ } ++ } ++ ++ event.setCancelled(cancelled); ++ Container oldContainer = this.player.openContainer; // SPIGOT-1224 ++ server.getPluginManager().callEvent(event); ++ if (this.player.openContainer != oldContainer) { ++ return; ++ } ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ case DEFAULT: ++ itemstack = this.player.openContainer.slotClick(packetIn.getSlotId(), packetIn.getUsedButton(), packetIn.getClickType(), this.player); ++ break; ++ case DENY: ++ /* Needs enum constructor in InventoryAction ++ if (action.modifiesOtherSlots()) { ++ ++ } else { ++ if (action.modifiesCursor()) { ++ this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried())); ++ } ++ if (action.modifiesClicked()) { ++ this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem())); ++ } ++ }*/ ++ switch (action) { ++ // Modified other slots ++ case PICKUP_ALL: ++ case MOVE_TO_OTHER_INVENTORY: ++ case HOTBAR_MOVE_AND_READD: ++ case HOTBAR_SWAP: ++ case COLLECT_TO_CURSOR: ++ case UNKNOWN: ++ this.player.sendContainerToPlayer(this.player.openContainer); ++ break; ++ // Modified cursor and clicked ++ case PICKUP_SOME: ++ case PICKUP_HALF: ++ case PICKUP_ONE: ++ case PLACE_ALL: ++ case PLACE_SOME: ++ case PLACE_ONE: ++ case SWAP_WITH_CURSOR: ++ this.player.connection.sendPacket(new SPacketSetSlot(-1, -1, this.player.inventory.getItemStack())); ++ this.player.connection.sendPacket(new SPacketSetSlot(this.player.openContainer.windowId, packetIn.getSlotId(), this.player.openContainer.getSlot(packetIn.getSlotId()).getStack())); ++ break; ++ // Modified clicked only ++ case DROP_ALL_SLOT: ++ case DROP_ONE_SLOT: ++ this.player.connection.sendPacket(new SPacketSetSlot(this.player.openContainer.windowId, packetIn.getSlotId(), this.player.openContainer.getSlot(packetIn.getSlotId()).getStack())); ++ break; ++ // Modified cursor only ++ case DROP_ALL_CURSOR: ++ case DROP_ONE_CURSOR: ++ case CLONE_STACK: ++ this.player.connection.sendPacket(new SPacketSetSlot(-1, -1, this.player.inventory.getItemStack())); ++ break; ++ // Nothing ++ case NOTHING: ++ break; ++ } ++ return; ++ } ++ ++ if (event instanceof CraftItemEvent) { ++ // Need to update the inventory on crafting to ++ // correctly support custom recipes ++ player.sendContainerToPlayer(player.openContainer); ++ } ++ } ++ // CraftBukkit end ++ ++ if (ItemStack.areItemStacksEqualUsingNBTShareTag(packetIn.getClickedItem(), itemstack)) + { + this.player.connection.sendPacket(new SPacketConfirmTransaction(packetIn.getWindowId(), packetIn.getActionNumber(), true)); + this.player.isChangingQuantityOnly = true; +@@ -1178,8 +2186,8 @@ + + for (int j = 0; j < this.player.openContainer.inventorySlots.size(); ++j) + { +- ItemStack itemstack = ((Slot)this.player.openContainer.inventorySlots.get(j)).getStack(); +- ItemStack itemstack1 = itemstack.isEmpty() ? ItemStack.EMPTY : itemstack; ++ ItemStack itemstack2 = ((Slot)this.player.openContainer.inventorySlots.get(j)).getStack(); ++ ItemStack itemstack1 = itemstack2.isEmpty() ? ItemStack.EMPTY : itemstack2; + nonnulllist1.add(itemstack1); + } + +@@ -1203,6 +2211,7 @@ + public void processEnchantItem(CPacketEnchantItem packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + this.player.markPlayerActive(); + + if (this.player.openContainer.windowId == packetIn.getWindowId() && this.player.openContainer.getCanCraft(this.player) && !this.player.isSpectator()) +@@ -1242,8 +2251,47 @@ + } + + boolean flag1 = packetIn.getSlotId() >= 1 && packetIn.getSlotId() <= 45; +- boolean flag2 = itemstack.isEmpty() || itemstack.getMetadata() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty(); ++ // boolean flag2 = itemstack.isEmpty() || itemstack.getMetadata() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty(); ++ // CraftBukkit - Add invalidItems check ++ boolean flag2 = (itemstack.isEmpty() || itemstack.getMetadata() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty() && !invalidItems.contains(Item.getIdFromItem(itemstack.getItem())) || !org.spigotmc.SpigotConfig.filterCreativeItems); // Spigot ++ if (flag || (flag1 && !ItemStack.areItemStacksEqual(this.player.inventoryContainer.getSlot(packetIn.getSlotId()).getStack(), packetIn.getStack()))) { // Insist on valid slot ++ // CraftBukkit start - Call click event ++ InventoryView inventory = this.player.inventoryContainer.getBukkitView(); ++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packetIn.getStack()); + ++ InventoryType.SlotType type = InventoryType.SlotType.QUICKBAR; ++ if (flag) { ++ type = InventoryType.SlotType.OUTSIDE; ++ } else if (packetIn.getSlotId() < 36) { ++ if (packetIn.getSlotId() >= 5 && packetIn.getSlotId() < 9) { ++ type = InventoryType.SlotType.ARMOR; ++ } else { ++ type = InventoryType.SlotType.CONTAINER; ++ } ++ } ++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packetIn.getSlotId(), item); ++ server.getPluginManager().callEvent(event); ++ ++ itemstack = CraftItemStack.asNMSCopy(event.getCursor()); ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ // Plugin cleared the id / stacksize checks ++ flag2 = true; ++ break; ++ case DEFAULT: ++ break; ++ case DENY: ++ // Reset the slot ++ if (packetIn.getSlotId() >= 0) { ++ this.player.connection.sendPacket(new SPacketSetSlot(this.player.inventoryContainer.windowId, packetIn.getSlotId(), this.player.inventoryContainer.getSlot(packetIn.getSlotId()).getStack())); ++ this.player.connection.sendPacket(new SPacketSetSlot(-1, -1, ItemStack.EMPTY)); ++ } ++ return; ++ } ++ } ++ // CraftBukkit end ++ + if (flag1 && flag2) + { + if (itemstack.isEmpty()) +@@ -1273,6 +2321,7 @@ + public void processConfirmTransaction(CPacketConfirmTransaction packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + Short oshort = this.pendingTransactions.lookup(this.player.openContainer.windowId); + + if (oshort != null && packetIn.getUid() == oshort.shortValue() && this.player.openContainer.windowId == packetIn.getWindowId() && !this.player.openContainer.getCanCraft(this.player) && !this.player.isSpectator()) +@@ -1284,6 +2333,7 @@ + public void processUpdateSign(CPacketUpdateSign packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (this.player.isMovementBlocked()) return; + this.player.markPlayerActive(); + WorldServer worldserver = this.serverController.getWorld(this.player.dimension); + BlockPos blockpos = packetIn.getPosition(); +@@ -1300,19 +2350,35 @@ + + TileEntitySign tileentitysign = (TileEntitySign)tileentity; + +- if (!tileentitysign.getIsEditable() || tileentitysign.getPlayer() != this.player) ++ if (!tileentitysign.getIsEditable() || tileentitysign.signEditor == null || !tileentitysign.signEditor.equals(this.player.getUniqueID())) // Paper + { + this.serverController.logWarning("Player " + this.player.getName() + " just tried to change non-editable sign"); ++ this.sendPacket(tileentity.getUpdatePacket()); + return; + } + + String[] astring = packetIn.getLines(); + ++ Player player = this.server.getPlayer(this.player); ++ int x = packetIn.getPosition().getX(); ++ int y = packetIn.getPosition().getY(); ++ int z = packetIn.getPosition().getZ(); ++ String[] lines = new String[4]; ++ + for (int i = 0; i < astring.length; ++i) + { +- tileentitysign.signText[i] = new TextComponentString(TextFormatting.getTextWithoutFormattingCodes(astring[i])); ++ // tileentitysign.signText[i] = new TextComponentString(TextFormatting.getTextWithoutFormattingCodes(astring[i])); ++ lines[i] = TextFormatting.getTextWithoutFormattingCodes(new TextComponentString(TextFormatting.getTextWithoutFormattingCodes(astring[i])).getUnformattedText()); + } + ++ SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.server.getPlayer(this.player), lines); ++ this.server.getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ System.arraycopy(org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.getLines()), 0, tileentitysign.signText, 0, 4); ++ tileentitysign.isEditable = false; ++ } ++ + tileentitysign.markDirty(); + worldserver.notifyBlockUpdate(blockpos, iblockstate, iblockstate, 3); + } +@@ -1320,6 +2386,7 @@ + + public void processKeepAlive(CPacketKeepAlive packetIn) + { ++ //PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); // CraftBukkit // Paper - This shouldn't be on the main thread + if (this.field_194403_g && packetIn.getKey() == this.field_194404_h) + { + int i = (int)(this.currentTimeMillis() - this.field_194402_f); +@@ -1328,7 +2395,11 @@ + } + else if (!this.player.getName().equals(this.serverController.getServerOwner())) + { +- this.disconnect(new TextComponentTranslation("disconnect.timeout", new Object[0])); ++ // Paper start - This needs to be handled on the main thread for plugins ++ LOGGER.warn("{} sent an invalid keepalive! pending keepalive: {} got id: {} expected id: {}", ++ this.player.getName(), this.field_194403_g, packetIn.getKey(), this.field_194404_h); ++ serverController.addScheduledTask(() -> this.disconnect(new TextComponentTranslation("disconnect.timeout", new Object[0]))); ++ // Paper end + } + } + +@@ -1340,12 +2411,25 @@ + public void processPlayerAbilities(CPacketPlayerAbilities packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); +- this.player.capabilities.isFlying = packetIn.isFlying() && this.player.capabilities.allowFlying; ++ // this.player.capabilities.isFlying = packetIn.isFlying() && this.player.capabilities.allowFlying; ++ if (this.player.capabilities.allowFlying && this.player.capabilities.isFlying != packetIn.isFlying()) { ++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.server.getPlayer(this.player), packetIn.isFlying()); ++ this.server.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.player.capabilities.isFlying = packetIn.isFlying(); // Actually set the player's flying status ++ } else { ++ this.player.sendPlayerAbilities(); // Tell the player their ability was reverted ++ } ++ } + } + + public void processTabComplete(CPacketTabComplete packetIn) + { + PacketThreadUtil.checkThreadAndEnqueue(packetIn, this, this.player.getServerWorld()); ++ if (chatSpamField.addAndGet(this, 10) > 500 && !this.serverController.getPlayerList().canSendCommands(this.player.getGameProfile())) { ++ this.disconnect(new TextComponentTranslation("disconnect.spam", new Object[0])); ++ return; ++ } + List list = Lists.newArrayList(); + + for (String s : this.serverController.getTabCompletions(this.player, packetIn.getMessage(), packetIn.getTargetBlock(), packetIn.hasTargetBlock())) +@@ -1369,6 +2453,11 @@ + + if ("MC|BEdit".equals(s)) + { ++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ this.disconnect("Book edited too quickly!"); ++ return; ++ } ++ this.lastBookTick = MinecraftServer.currentTick; + PacketBuffer packetbuffer = packetIn.getBufferData(); + + try +@@ -1395,15 +2484,22 @@ + if (itemstack.getItem() == Items.WRITABLE_BOOK && itemstack.getItem() == itemstack1.getItem()) + { + itemstack1.setTagInfo("pages", itemstack.getTagCompound().getTagList("pages", 8)); ++ CraftEventFactory.handleEditBookEvent(player, itemstack1); + } + } + catch (Exception exception6) + { + LOGGER.error("Couldn't handle book info", (Throwable)exception6); ++ this.disconnect("Invalid book data!"); + } + } + else if ("MC|BSign".equals(s)) + { ++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ this.disconnect("Book edited too quickly!"); ++ return; ++ } ++ this.lastBookTick = MinecraftServer.currentTick; + PacketBuffer packetbuffer1 = packetIn.getBufferData(); + + try +@@ -1443,12 +2539,14 @@ + } + + itemstack2.setTagInfo("pages", nbttaglist); +- this.player.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, itemstack2); ++ // this.player.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, itemstack2); ++ CraftEventFactory.handleEditBookEvent(player, itemstack2); + } + } + catch (Exception exception7) + { + LOGGER.error("Couldn't sign book", (Throwable)exception7); ++ this.disconnect("Invalid book data!"); + } + } + else if ("MC|TrSel".equals(s)) +@@ -1466,6 +2564,7 @@ + catch (Exception exception5) + { + LOGGER.error("Couldn't select trade", (Throwable)exception5); ++ this.disconnect("Invalid trade data!"); + } + } + else if ("MC|AdvCmd".equals(s)) +@@ -1528,6 +2627,7 @@ + catch (Exception exception4) + { + LOGGER.error("Couldn't set command block", (Throwable)exception4); ++ this.disconnect("Invalid command data!"); + } + } + else if ("MC|AutoCmd".equals(s)) +@@ -1606,6 +2706,7 @@ + catch (Exception exception3) + { + LOGGER.error("Couldn't set command block", (Throwable)exception3); ++ this.disconnect("Invalid command data!"); + } + } + else if ("MC|Beacon".equals(s)) +@@ -1632,6 +2733,7 @@ + catch (Exception exception2) + { + LOGGER.error("Couldn't set beacon", (Throwable)exception2); ++ this.disconnect("Invalid beacon data!"); + } + } + } +@@ -1743,6 +2845,7 @@ + catch (Exception exception1) + { + LOGGER.error("Couldn't set structure block", (Throwable)exception1); ++ this.disconnect("Invalid structure data!"); + } + } + else if ("MC|PickItem".equals(s)) +@@ -1760,7 +2863,43 @@ + catch (Exception exception) + { + LOGGER.error("Couldn't pick item", (Throwable)exception); ++ this.disconnect("Invalid pick item!"); + } + } ++ // CraftBukkit start ++ else if (packetIn.getChannelName().equals("REGISTER")) { ++ try { ++ String channels = packetIn.getBufferData().toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ getPlayer().addChannel(channel); ++ } ++ } catch (Exception ex) { ++ NetHandlerPlayServer.LOGGER.error("Couldn\'t register custom payload", ex); ++ this.disconnect("Invalid payload REGISTER!"); ++ } ++ } else if (packetIn.getChannelName().equals("UNREGISTER")) { ++ try { ++ String channels = packetIn.getBufferData().toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ getPlayer().removeChannel(channel); ++ } ++ } catch (Exception ex) { ++ NetHandlerPlayServer.LOGGER.error("Couldn\'t unregister custom payload", ex); ++ this.disconnect("Invalid payload UNREGISTER!"); ++ } ++ } else { ++ try { ++ byte[] data = new byte[packetIn.getBufferData().readableBytes()]; ++ packetIn.getBufferData().readBytes(data); ++ server.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packetIn.getChannelName(), data); ++ } catch (Exception ex) { ++ NetHandlerPlayServer.LOGGER.error("Couldn\'t dispatch custom payload", ex); ++ this.disconnect("Invalid custom payload!"); ++ } ++ } + } ++ ++ public final boolean isDisconnected() { ++ return !this.player.joining && !this.netManager.isChannelOpen(); ++ } + } diff --git a/patches/net/minecraft/network/NetworkManager.java.patch b/patches/net/minecraft/network/NetworkManager.java.patch new file mode 100644 index 00000000..4178ab9b --- /dev/null +++ b/patches/net/minecraft/network/NetworkManager.java.patch @@ -0,0 +1,102 @@ +--- ../src-base/minecraft/net/minecraft/network/NetworkManager.java ++++ ../src-work/minecraft/net/minecraft/network/NetworkManager.java +@@ -74,15 +74,25 @@ + } + }; + private final EnumPacketDirection direction; +- private final Queue outboundPacketsQueue = Queues.newConcurrentLinkedQueue(); ++ private final Queue outboundPacketsQueue = Queues.newConcurrentLinkedQueue(); + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); +- private Channel channel; +- private SocketAddress socketAddress; ++ public Channel channel; ++ // Spigot Start // PAIL ++ public SocketAddress socketAddress; ++ public java.util.UUID spoofedUUID; ++ public com.mojang.authlib.properties.Property[] spoofedProfile; ++ // Spigot End ++ public boolean preparing = true; // Spigot + private INetHandler packetListener; + private ITextComponent terminationReason; + private boolean isEncrypted; + private boolean disconnected; + ++ // Paper start - NetworkClient implementation ++ public int protocolVersion; ++ public java.net.InetSocketAddress virtualHost; ++ // Paper end ++ + public NetworkManager(EnumPacketDirection packetDirection) + { + this.direction = packetDirection; +@@ -98,6 +108,7 @@ + super.channelActive(p_channelActive_1_); + this.channel = p_channelActive_1_.channel(); + this.socketAddress = this.channel.remoteAddress(); ++ this.preparing = false; // Spigot + + try + { +@@ -173,7 +184,7 @@ + + try + { +- this.outboundPacketsQueue.add(new NetworkManager.InboundHandlerTuplePacketListener(packetIn, new GenericFutureListener[0])); ++ this.outboundPacketsQueue.add(new InboundHandlerTuplePacketListener(packetIn, new GenericFutureListener[0])); + } + finally + { +@@ -195,7 +206,7 @@ + + try + { +- this.outboundPacketsQueue.add(new NetworkManager.InboundHandlerTuplePacketListener(packetIn, (GenericFutureListener[])ArrayUtils.add(listeners, 0, listener))); ++ this.outboundPacketsQueue.add(new InboundHandlerTuplePacketListener(packetIn, (GenericFutureListener[])ArrayUtils.add(listeners, 0, listener))); + } + finally + { +@@ -265,7 +276,7 @@ + { + while (!this.outboundPacketsQueue.isEmpty()) + { +- NetworkManager.InboundHandlerTuplePacketListener networkmanager$inboundhandlertuplepacketlistener = this.outboundPacketsQueue.poll(); ++ InboundHandlerTuplePacketListener networkmanager$inboundhandlertuplepacketlistener = this.outboundPacketsQueue.poll(); + this.dispatchPacket(networkmanager$inboundhandlertuplepacketlistener.packet, networkmanager$inboundhandlertuplepacketlistener.futureListeners); + } + } +@@ -298,9 +309,12 @@ + + public void closeChannel(ITextComponent message) + { ++ this.preparing = false; // Spigot + if (this.channel.isOpen()) + { +- this.channel.close().awaitUninterruptibly(); ++ // We can't wait as this may be called from an event loop. ++ // this.channel.close().awaitUninterruptibly(); ++ this.channel.close(); + this.terminationReason = message; + } + } +@@ -410,7 +424,7 @@ + } + else + { +- this.channel.pipeline().addBefore("decoder", "decompress", new NettyCompressionDecoder(threshold)); ++ this.channel.pipeline().addAfter("splitter", "decompress", new NettyCompressionDecoder(threshold)); + } + + if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) +@@ -476,4 +490,12 @@ + this.futureListeners = inFutureListeners; + } + } ++ ++ // Spigot Start ++ public SocketAddress getRawAddress() ++ { ++ return this.channel.remoteAddress(); ++ } ++ // Spigot End ++ + } diff --git a/patches/net/minecraft/network/NetworkSystem.java.patch b/patches/net/minecraft/network/NetworkSystem.java.patch new file mode 100644 index 00000000..d85dca10 --- /dev/null +++ b/patches/net/minecraft/network/NetworkSystem.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/network/NetworkSystem.java ++++ ../src-work/minecraft/net/minecraft/network/NetworkSystem.java +@@ -165,6 +165,13 @@ + { + synchronized (this.networkManagers) + { ++ // Spigot Start ++ // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order ++ if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) ++ { ++ Collections.shuffle(this.networkManagers); ++ } ++ // Spigot End + Iterator iterator = this.networkManagers.iterator(); + + while (iterator.hasNext()) +@@ -209,6 +216,9 @@ + } + else + { ++ // Spigot Start - Fix a race condition where a NetworkManager could be unregistered just before connection. ++ if (networkmanager.preparing) continue; ++ // Spigot End + iterator.remove(); + networkmanager.checkDisconnected(); + } diff --git a/patches/net/minecraft/network/PacketBuffer.java.patch b/patches/net/minecraft/network/PacketBuffer.java.patch new file mode 100644 index 00000000..c00d8ed0 --- /dev/null +++ b/patches/net/minecraft/network/PacketBuffer.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/network/PacketBuffer.java ++++ ../src-work/minecraft/net/minecraft/network/PacketBuffer.java +@@ -30,6 +30,7 @@ + import net.minecraft.util.text.ITextComponent; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + + public class PacketBuffer extends ByteBuf + { +@@ -287,9 +288,9 @@ + { + CompressedStreamTools.write(nbt, new ByteBufOutputStream(this)); + } +- catch (IOException ioexception) ++ catch (Exception exception) // CraftBukkit - IOException -> Exception + { +- throw new EncoderException(ioexception); ++ throw new EncoderException(exception); + } + } + +@@ -359,6 +360,8 @@ + int k = this.readShort(); + ItemStack itemstack = new ItemStack(Item.getItemById(i), j, k); + itemstack.getItem().readNBTShareTag(itemstack, this.readCompoundTag()); ++ if (itemstack.getTagCompound() != null) ++ CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); + return itemstack; + } + } diff --git a/patches/net/minecraft/network/datasync/EntityDataManager.java.patch b/patches/net/minecraft/network/datasync/EntityDataManager.java.patch new file mode 100644 index 00000000..39125067 --- /dev/null +++ b/patches/net/minecraft/network/datasync/EntityDataManager.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraft/network/datasync/EntityDataManager.java ++++ ../src-work/minecraft/net/minecraft/network/datasync/EntityDataManager.java +@@ -4,6 +4,7 @@ + import com.google.common.collect.Maps; + import io.netty.handler.codec.DecoderException; + import io.netty.handler.codec.EncoderException; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import java.io.IOException; + import java.util.List; + import java.util.Map; +@@ -26,7 +27,7 @@ + private static final Logger LOGGER = LogManager.getLogger(); + private static final Map < Class , Integer > NEXT_ID_MAP = Maps. < Class , Integer > newHashMap(); + private final Entity entity; +- private final Map < Integer, EntityDataManager.DataEntry> entries = Maps. < Integer, EntityDataManager.DataEntry> newHashMap(); ++ private final Map < Integer, EntityDataManager.DataEntry> entries = new Int2ObjectOpenHashMap<>(); // Paper + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private boolean empty = true; + private boolean dirty; diff --git a/patches/net/minecraft/network/handshake/client/C00Handshake.java.patch b/patches/net/minecraft/network/handshake/client/C00Handshake.java.patch new file mode 100644 index 00000000..c7a064f1 --- /dev/null +++ b/patches/net/minecraft/network/handshake/client/C00Handshake.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/network/handshake/client/C00Handshake.java ++++ ../src-work/minecraft/net/minecraft/network/handshake/client/C00Handshake.java +@@ -11,8 +11,8 @@ + public class C00Handshake implements Packet + { + private int protocolVersion; +- private String ip; +- private int port; ++ public String ip; ++ public int port; + private EnumConnectionState requestedState; + private boolean hasFMLMarker = false; + +@@ -38,11 +38,15 @@ + public void readPacketData(PacketBuffer buf) throws IOException + { + this.protocolVersion = buf.readVarInt(); +- this.ip = buf.readString(255); ++ this.ip = buf.readString(Short.MAX_VALUE); // Spigot + this.port = buf.readUnsignedShort(); + this.requestedState = EnumConnectionState.getById(buf.readVarInt()); + this.hasFMLMarker = this.ip.contains("\0FML\0"); +- this.ip = this.ip.split("\0")[0]; ++ if(this.hasFMLMarker){ ++ this.ip = this.ip.replace("\0FML\0", ""); ++ }else if(this.ip.split("\0").length > 2){ ++ this.hasFMLMarker = true; // Kettle - Bungee Support ++ } + } + + public void writePacketData(PacketBuffer buf) throws IOException diff --git a/patches/net/minecraft/network/play/client/CPacketChatMessage.java.patch b/patches/net/minecraft/network/play/client/CPacketChatMessage.java.patch new file mode 100644 index 00000000..ebe71e7e --- /dev/null +++ b/patches/net/minecraft/network/play/client/CPacketChatMessage.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/network/play/client/CPacketChatMessage.java ++++ ../src-work/minecraft/net/minecraft/network/play/client/CPacketChatMessage.java +@@ -1,6 +1,9 @@ + package net.minecraft.network.play.client; + ++import com.google.common.util.concurrent.ThreadFactoryBuilder; + import java.io.IOException; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; + import net.minecraft.network.Packet; + import net.minecraft.network.PacketBuffer; + import net.minecraft.network.play.INetHandlerPlayServer; +@@ -33,8 +36,17 @@ + buf.writeString(this.message); + } + +- public void processPacket(INetHandlerPlayServer handler) ++ // Spigot Start ++ private static final ExecutorService executors = Executors.newCachedThreadPool( ++ new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").build()); ++ ++ public void processPacket(final INetHandlerPlayServer handler) { ++ if ( !message.startsWith("/") ) + { ++ executors.submit(() -> handler.processChatMessage( CPacketChatMessage.this )); ++ return; ++ } ++ // Spigot End + handler.processChatMessage(this); + } + diff --git a/patches/net/minecraft/network/play/client/CPacketPlayer.java.patch b/patches/net/minecraft/network/play/client/CPacketPlayer.java.patch new file mode 100644 index 00000000..e787ee25 --- /dev/null +++ b/patches/net/minecraft/network/play/client/CPacketPlayer.java.patch @@ -0,0 +1,24 @@ +--- ../src-base/minecraft/net/minecraft/network/play/client/CPacketPlayer.java ++++ ../src-work/minecraft/net/minecraft/network/play/client/CPacketPlayer.java +@@ -9,14 +9,14 @@ + + public class CPacketPlayer implements Packet + { +- protected double x; +- protected double y; +- protected double z; +- protected float yaw; +- protected float pitch; ++ public double x; ++ public double y; ++ public double z; ++ public float yaw; ++ public float pitch; + protected boolean onGround; +- protected boolean moving; +- protected boolean rotating; ++ public boolean moving; ++ public boolean rotating; + + public CPacketPlayer() + { diff --git a/patches/net/minecraft/network/play/client/CPacketResourcePackStatus.java.patch b/patches/net/minecraft/network/play/client/CPacketResourcePackStatus.java.patch new file mode 100644 index 00000000..82362007 --- /dev/null +++ b/patches/net/minecraft/network/play/client/CPacketResourcePackStatus.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/network/play/client/CPacketResourcePackStatus.java ++++ ../src-work/minecraft/net/minecraft/network/play/client/CPacketResourcePackStatus.java +@@ -7,7 +7,7 @@ + + public class CPacketResourcePackStatus implements Packet + { +- private CPacketResourcePackStatus.Action action; ++ public Action action; + + public CPacketResourcePackStatus() + { diff --git a/patches/net/minecraft/network/play/server/SPacketChat.java.patch b/patches/net/minecraft/network/play/server/SPacketChat.java.patch new file mode 100644 index 00000000..4099082e --- /dev/null +++ b/patches/net/minecraft/network/play/server/SPacketChat.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/network/play/server/SPacketChat.java ++++ ../src-work/minecraft/net/minecraft/network/play/server/SPacketChat.java +@@ -12,6 +12,7 @@ + public class SPacketChat implements Packet + { + private ITextComponent chatComponent; ++ public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot + private ChatType type; + + public SPacketChat() +@@ -37,7 +38,13 @@ + + public void writePacketData(PacketBuffer buf) throws IOException + { +- buf.writeTextComponent(this.chatComponent); ++ // Spigot start ++ if (components != null) { ++ buf.writeString(net.md_5.bungee.chat.ComponentSerializer.toString(components)); ++ } else { ++ buf.writeTextComponent(this.chatComponent); ++ } ++ // Spigot end + buf.writeByte(this.type.getId()); + } + diff --git a/patches/net/minecraft/network/play/server/SPacketSpawnPosition.java.patch b/patches/net/minecraft/network/play/server/SPacketSpawnPosition.java.patch new file mode 100644 index 00000000..9f655dc7 --- /dev/null +++ b/patches/net/minecraft/network/play/server/SPacketSpawnPosition.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/network/play/server/SPacketSpawnPosition.java ++++ ../src-work/minecraft/net/minecraft/network/play/server/SPacketSpawnPosition.java +@@ -10,7 +10,7 @@ + + public class SPacketSpawnPosition implements Packet + { +- private BlockPos spawnBlockPos; ++ public BlockPos spawnBlockPos; + + public SPacketSpawnPosition() + { diff --git a/patches/net/minecraft/network/play/server/SPacketWorldBorder.java.patch b/patches/net/minecraft/network/play/server/SPacketWorldBorder.java.patch new file mode 100644 index 00000000..f49eff09 --- /dev/null +++ b/patches/net/minecraft/network/play/server/SPacketWorldBorder.java.patch @@ -0,0 +1,46 @@ +--- ../src-base/minecraft/net/minecraft/network/play/server/SPacketWorldBorder.java ++++ ../src-work/minecraft/net/minecraft/network/play/server/SPacketWorldBorder.java +@@ -4,13 +4,14 @@ + import net.minecraft.network.Packet; + import net.minecraft.network.PacketBuffer; + import net.minecraft.network.play.INetHandlerPlayClient; ++import net.minecraft.world.WorldProviderHell; + import net.minecraft.world.border.WorldBorder; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + + public class SPacketWorldBorder implements Packet + { +- private SPacketWorldBorder.Action action; ++ private Action action; + private int size; + private double centerX; + private double centerZ; +@@ -24,11 +25,15 @@ + { + } + +- public SPacketWorldBorder(WorldBorder border, SPacketWorldBorder.Action actionIn) ++ public SPacketWorldBorder(WorldBorder border, Action actionIn) + { + this.action = actionIn; +- this.centerX = border.getCenterX(); +- this.centerZ = border.getCenterZ(); ++ // CraftBukkit start - multiply out nether border ++ // this.centerX = border.getCenterX(); ++ // this.centerZ = border.getCenterZ(); ++ this.centerX = border.getCenterX() * (border.world.provider instanceof WorldProviderHell ? 8 : 1); ++ this.centerZ = border.getCenterZ() * (border.world.provider instanceof WorldProviderHell ? 8 : 1); ++ // CraftBukkit end + this.diameter = border.getDiameter(); + this.targetSize = border.getTargetSize(); + this.timeUntilTarget = border.getTimeUntilTarget(); +@@ -39,7 +44,7 @@ + + public void readPacketData(PacketBuffer buf) throws IOException + { +- this.action = (SPacketWorldBorder.Action)buf.readEnumValue(SPacketWorldBorder.Action.class); ++ this.action = (Action)buf.readEnumValue(Action.class); + + switch (this.action) + { diff --git a/patches/net/minecraft/network/rcon/RConConsoleSource.java.patch b/patches/net/minecraft/network/rcon/RConConsoleSource.java.patch new file mode 100644 index 00000000..5e5a76e0 --- /dev/null +++ b/patches/net/minecraft/network/rcon/RConConsoleSource.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/network/rcon/RConConsoleSource.java ++++ ../src-work/minecraft/net/minecraft/network/rcon/RConConsoleSource.java +@@ -22,6 +22,10 @@ + return "Rcon"; + } + ++ public void sendMessage(String message) { ++ this.buffer.append(message); ++ } ++ + public void sendMessage(ITextComponent component) + { + this.buffer.append(component.getUnformattedText()); diff --git a/patches/net/minecraft/network/rcon/RConThreadMain.java.patch b/patches/net/minecraft/network/rcon/RConThreadMain.java.patch new file mode 100644 index 00000000..1084c287 --- /dev/null +++ b/patches/net/minecraft/network/rcon/RConThreadMain.java.patch @@ -0,0 +1,38 @@ +--- ../src-base/minecraft/net/minecraft/network/rcon/RConThreadMain.java ++++ ../src-work/minecraft/net/minecraft/network/rcon/RConThreadMain.java +@@ -23,26 +23,26 @@ + private final String rconPassword; + private Map clientThreads; + +- public RConThreadMain(IServer p_i1538_1_) ++ public RConThreadMain(IServer iserver) + { +- super(p_i1538_1_, "RCON Listener"); +- this.rconPort = p_i1538_1_.getIntProperty("rcon.port", 0); +- this.rconPassword = p_i1538_1_.getStringProperty("rcon.password", ""); +- this.hostname = p_i1538_1_.getHostname(); +- this.serverPort = p_i1538_1_.getPort(); ++ super(iserver, "RCON Listener"); ++ this.rconPort = iserver.getIntProperty("rcon.port", 0); ++ this.rconPassword = iserver.getStringProperty("rcon.password", ""); ++ this.hostname = iserver.getStringProperty("rcon.ip", iserver.getHostname()); // Paper ++ this.serverPort = iserver.getPort(); + + if (0 == this.rconPort) + { + this.rconPort = this.serverPort + 10; + this.logInfo("Setting default rcon port to " + this.rconPort); +- p_i1538_1_.setProperty("rcon.port", Integer.valueOf(this.rconPort)); ++ iserver.setProperty("rcon.port", Integer.valueOf(this.rconPort)); + + if (this.rconPassword.isEmpty()) + { +- p_i1538_1_.setProperty("rcon.password", ""); ++ iserver.setProperty("rcon.password", ""); + } + +- p_i1538_1_.saveProperties(); ++ iserver.saveProperties(); + } + + if (this.hostname.isEmpty()) diff --git a/patches/net/minecraft/pathfinding/FlyingNodeProcessor.java.patch b/patches/net/minecraft/pathfinding/FlyingNodeProcessor.java.patch new file mode 100644 index 00000000..f73b9330 --- /dev/null +++ b/patches/net/minecraft/pathfinding/FlyingNodeProcessor.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/pathfinding/FlyingNodeProcessor.java ++++ ../src-work/minecraft/net/minecraft/pathfinding/FlyingNodeProcessor.java +@@ -272,9 +272,7 @@ + EnumSet enumset = EnumSet.noneOf(PathNodeType.class); + PathNodeType pathnodetype = PathNodeType.BLOCKED; + BlockPos blockpos = new BlockPos(entitylivingIn); +- this.currentEntity = entitylivingIn; + pathnodetype = this.getPathNodeType(blockaccessIn, x, y, z, xSize, ySize, zSize, canBreakDoorsIn, canEnterDoorsIn, enumset, pathnodetype, blockpos); +- this.currentEntity = null; + + if (enumset.contains(PathNodeType.FENCE)) + { +@@ -323,7 +321,6 @@ + { + pathnodetype = PathNodeType.DAMAGE_CACTUS; + } +- else if (pathnodetype1 == PathNodeType.DAMAGE_OTHER) pathnodetype = PathNodeType.DAMAGE_OTHER; + else + { + pathnodetype = pathnodetype1 != PathNodeType.WALKABLE && pathnodetype1 != PathNodeType.OPEN && pathnodetype1 != PathNodeType.WATER ? PathNodeType.WALKABLE : PathNodeType.OPEN; diff --git a/patches/net/minecraft/pathfinding/WalkNodeProcessor.java.patch b/patches/net/minecraft/pathfinding/WalkNodeProcessor.java.patch new file mode 100644 index 00000000..43391f60 --- /dev/null +++ b/patches/net/minecraft/pathfinding/WalkNodeProcessor.java.patch @@ -0,0 +1,62 @@ +--- ../src-base/minecraft/net/minecraft/pathfinding/WalkNodeProcessor.java ++++ ../src-work/minecraft/net/minecraft/pathfinding/WalkNodeProcessor.java +@@ -23,7 +23,6 @@ + public class WalkNodeProcessor extends NodeProcessor + { + protected float avoidsWater; +- protected EntityLiving currentEntity; + + public void init(IBlockAccess sourceIn, EntityLiving mob) + { +@@ -296,9 +295,7 @@ + PathNodeType pathnodetype = PathNodeType.BLOCKED; + double d0 = (double)entitylivingIn.width / 2.0D; + BlockPos blockpos = new BlockPos(entitylivingIn); +- this.currentEntity = entitylivingIn; + pathnodetype = this.getPathNodeType(blockaccessIn, x, y, z, xSize, ySize, zSize, canBreakDoorsIn, canEnterDoorsIn, enumset, pathnodetype, blockpos); +- this.currentEntity = null; + + if (enumset.contains(PathNodeType.FENCE)) + { +@@ -402,8 +399,6 @@ + { + pathnodetype = PathNodeType.DAMAGE_CACTUS; + } +- +- if (pathnodetype1 == PathNodeType.DAMAGE_OTHER) pathnodetype = PathNodeType.DAMAGE_OTHER; + } + + pathnodetype = this.checkNeighborBlocks(blockaccessIn, x, y, z, pathnodetype); +@@ -422,19 +417,17 @@ + { + if (i != 0 || j != 0) + { +- IBlockState state = p_193578_1_.getBlockState(blockpos$pooledmutableblockpos.setPos(i + p_193578_2_, p_193578_3_, j + p_193578_4_)); +- Block block = state.getBlock(); +- PathNodeType type = block.getAiPathNodeType(state, p_193578_1_, blockpos$pooledmutableblockpos, this.currentEntity); ++ Block block = p_193578_1_.getBlockState(blockpos$pooledmutableblockpos.setPos(i + p_193578_2_, p_193578_3_, j + p_193578_4_)).getBlock(); + +- if (block == Blocks.CACTUS || type == PathNodeType.DAMAGE_CACTUS) ++ if (block == Blocks.CACTUS) + { + p_193578_5_ = PathNodeType.DANGER_CACTUS; + } +- else if (block == Blocks.FIRE || type == PathNodeType.DAMAGE_FIRE) ++ else if (block == Blocks.FIRE) + { + p_193578_5_ = PathNodeType.DANGER_FIRE; + } +- else if (type == PathNodeType.DAMAGE_OTHER) p_193578_5_ = PathNodeType.DANGER_OTHER; ++ else if(block.isBurning(p_193578_1_,blockpos$pooledmutableblockpos)) p_193578_5_ = PathNodeType.DAMAGE_FIRE; + } + } + } +@@ -451,7 +444,7 @@ + Block block = iblockstate.getBlock(); + Material material = iblockstate.getMaterial(); + +- PathNodeType type = block.getAiPathNodeType(iblockstate, p_189553_1_, blockpos, this.currentEntity); ++ PathNodeType type = block.getAiPathNodeType(iblockstate, p_189553_1_, blockpos); + if (type != null) return type; + + if (material == Material.AIR) diff --git a/patches/net/minecraft/potion/Potion.java.patch b/patches/net/minecraft/potion/Potion.java.patch new file mode 100644 index 00000000..7fb0391d --- /dev/null +++ b/patches/net/minecraft/potion/Potion.java.patch @@ -0,0 +1,138 @@ +--- ../src-base/minecraft/net/minecraft/potion/Potion.java ++++ ../src-work/minecraft/net/minecraft/potion/Potion.java +@@ -13,7 +13,9 @@ + import net.minecraft.entity.ai.attributes.IAttribute; + import net.minecraft.entity.ai.attributes.IAttributeInstance; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.MobEffects; ++import net.minecraft.network.play.server.SPacketUpdateHealth; + import net.minecraft.util.DamageSource; + import net.minecraft.util.ResourceLocation; + import net.minecraft.util.StringUtils; +@@ -21,6 +23,8 @@ + import net.minecraft.util.registry.RegistryNamespaced; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; + + public class Potion extends net.minecraftforge.registries.IForgeRegistryEntry.Impl + { +@@ -30,7 +34,7 @@ + private final int liquidColor; + private String name = ""; + private int statusIconIndex = -1; +- private double effectiveness; ++ public double effectiveness; + private boolean beneficial; + + @Nullable +@@ -78,14 +82,14 @@ + { + if (entityLivingBaseIn.getHealth() < entityLivingBaseIn.getMaxHealth()) + { +- entityLivingBaseIn.heal(1.0F); ++ entityLivingBaseIn.heal(1.0F, RegainReason.MAGIC_REGEN); + } + } + else if (this == MobEffects.POISON) + { + if (entityLivingBaseIn.getHealth() > 1.0F) + { +- entityLivingBaseIn.attackEntityFrom(DamageSource.MAGIC, 1.0F); ++ entityLivingBaseIn.attackEntityFrom(CraftEventFactory.POISON, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON + } + } + else if (this == MobEffects.WITHER) +@@ -100,7 +104,19 @@ + { + if (!entityLivingBaseIn.world.isRemote) + { +- ((EntityPlayer)entityLivingBaseIn).getFoodStats().addStats(amplifier + 1, 1.0F); ++ // ((EntityPlayer)entityLivingBaseIn).getFoodStats().addStats(amplifier + 1, 1.0F); ++ // CraftBukkit start ++ EntityPlayer entityhuman = (EntityPlayer) entityLivingBaseIn; ++ int oldFoodLevel = entityhuman.getFoodStats().foodLevel; ++ ++ org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); ++ ++ if (!event.isCancelled()) { ++ entityhuman.getFoodStats().addStats(event.getFoodLevel() - oldFoodLevel, 1.0F); ++ } ++ ++ ((EntityPlayerMP) entityhuman).connection.sendPacket(new SPacketUpdateHealth(((EntityPlayerMP) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodStats().foodLevel, entityhuman.getFoodStats().foodSaturationLevel)); ++ // CraftBukkit end + } + } + else if ((this != MobEffects.INSTANT_HEALTH || entityLivingBaseIn.isEntityUndead()) && (this != MobEffects.INSTANT_DAMAGE || !entityLivingBaseIn.isEntityUndead())) +@@ -112,7 +128,7 @@ + } + else + { +- entityLivingBaseIn.heal((float)Math.max(4 << amplifier, 0)); ++ entityLivingBaseIn.heal((float)Math.max(4 << amplifier, 0), RegainReason.MAGIC); + } + } + +@@ -328,29 +344,11 @@ + * @param y the y coordinate + * @param effect the active PotionEffect + * @param mc the Minecraft instance, for convenience +- * @deprecated use {@link #renderInventoryEffect(PotionEffect, net.minecraft.client.gui.Gui, int, int, float)} + */ + @SideOnly(Side.CLIENT) +- @Deprecated // TODO: remove + public void renderInventoryEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc) { } + + /** +- * Called to draw the this Potion onto the player's inventory when it's active. +- * This can be used to e.g. render Potion icons from your own texture. +- * +- * @param effect the active PotionEffect +- * @param gui the gui instance +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z level +- */ +- @SideOnly(Side.CLIENT) +- public void renderInventoryEffect(PotionEffect effect, net.minecraft.client.gui.Gui gui, int x, int y, float z) +- { +- renderInventoryEffect(x, y, effect, net.minecraft.client.Minecraft.getMinecraft()); +- } +- +- /** + * Called to draw the this Potion onto the player's ingame HUD when it's active. + * This can be used to e.g. render Potion icons from your own texture. + * @param x the x coordinate +@@ -358,30 +356,11 @@ + * @param effect the active PotionEffect + * @param mc the Minecraft instance, for convenience + * @param alpha the alpha value, blinks when the potion is about to run out +- * @deprecated use {@link #renderHUDEffect(PotionEffect, net.minecraft.client.gui.Gui, int, int, float, float)} + */ + @SideOnly(Side.CLIENT) +- @Deprecated // TODO: remove + public void renderHUDEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc, float alpha) { } + + /** +- * Called to draw the this Potion onto the player's ingame HUD when it's active. +- * This can be used to e.g. render Potion icons from your own texture. +- * +- * @param effect the active PotionEffect +- * @param gui the gui instance +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z level +- * @param alpha the alpha value, blinks when the potion is about to run out +- */ +- @SideOnly(Side.CLIENT) +- public void renderHUDEffect(PotionEffect effect, net.minecraft.client.gui.Gui gui, int x, int y, float z, float alpha) +- { +- renderHUDEffect(x, y, effect, net.minecraft.client.Minecraft.getMinecraft(), alpha); +- } +- +- /** + * Get a fresh list of items that can cure this Potion. + * All new PotionEffects created from this Potion will call this to initialize the default curative items + * @see PotionEffect#getCurativeItems diff --git a/patches/net/minecraft/profiler/Profiler.java.patch b/patches/net/minecraft/profiler/Profiler.java.patch new file mode 100644 index 00000000..93eb5cb2 --- /dev/null +++ b/patches/net/minecraft/profiler/Profiler.java.patch @@ -0,0 +1,119 @@ +--- ../src-base/minecraft/net/minecraft/profiler/Profiler.java ++++ ../src-work/minecraft/net/minecraft/profiler/Profiler.java +@@ -13,6 +13,7 @@ + + public class Profiler + { ++ public static final boolean ENABLED = Boolean.getBoolean("enableDebugMethodProfiler"); // CraftBukkit - disable unless specified in JVM arguments + private static final Logger LOGGER = LogManager.getLogger(); + private final List sectionList = Lists.newArrayList(); + private final List timestampList = Lists.newArrayList(); +@@ -22,6 +23,7 @@ + + public void clearProfiling() + { ++ if (!ENABLED) return; + this.profilingMap.clear(); + this.profilingSection = ""; + this.sectionList.clear(); +@@ -29,6 +31,7 @@ + + public void startSection(String name) + { ++ if (!ENABLED) return; + if (this.profilingEnabled) + { + if (!this.profilingSection.isEmpty()) +@@ -44,6 +47,7 @@ + + public void func_194340_a(Supplier p_194340_1_) + { ++ if (!ENABLED) return; + if (this.profilingEnabled) + { + this.startSection(p_194340_1_.get()); +@@ -52,6 +56,7 @@ + + public void endSection() + { ++ if (!ENABLED) return; + if (this.profilingEnabled) + { + long i = System.nanoTime(); +@@ -77,17 +82,17 @@ + } + } + +- public List getProfilingData(String profilerName) ++ public List getProfilingData(String profilerName) + { +- if (!this.profilingEnabled) ++ if (!ENABLED || !this.profilingEnabled) + { +- return Collections.emptyList(); ++ return Collections.emptyList(); + } + else + { + long i = this.profilingMap.containsKey("root") ? ((Long)this.profilingMap.get("root")).longValue() : 0L; + long j = this.profilingMap.containsKey(profilerName) ? ((Long)this.profilingMap.get(profilerName)).longValue() : -1L; +- List list = Lists.newArrayList(); ++ List list = Lists.newArrayList(); + + if (!profilerName.isEmpty()) + { +@@ -124,7 +129,7 @@ + double d0 = (double)l * 100.0D / (double)k; + double d1 = (double)l * 100.0D / (double)i; + String s2 = s1.substring(profilerName.length()); +- list.add(new Profiler.Result(s2, d0, d1)); ++ list.add(new Result(s2, d0, d1)); + } + } + +@@ -135,23 +140,25 @@ + + if ((float)k > f) + { +- list.add(new Profiler.Result("unspecified", (double)((float)k - f) * 100.0D / (double)k, (double)((float)k - f) * 100.0D / (double)i)); ++ list.add(new Result("unspecified", (double)((float)k - f) * 100.0D / (double)k, (double)((float)k - f) * 100.0D / (double)i)); + } + + Collections.sort(list); +- list.add(0, new Profiler.Result(profilerName, 100.0D, (double)k * 100.0D / (double)i)); ++ list.add(0, new Result(profilerName, 100.0D, (double)k * 100.0D / (double)i)); + return list; + } + } + + public void endStartSection(String name) + { ++ if (!ENABLED) return; + this.endSection(); + this.startSection(name); + } + + public String getNameOfLastSection() + { ++ if (!ENABLED) return "[DISABLED]"; + return this.sectionList.isEmpty() ? "[UNKNOWN]" : (String)this.sectionList.get(this.sectionList.size() - 1); + } + +@@ -162,7 +169,7 @@ + this.func_194340_a(p_194339_1_); + } + +- public static final class Result implements Comparable ++ public static final class Result implements Comparable + { + public double usePercentage; + public double totalUsePercentage; +@@ -175,7 +182,7 @@ + this.totalUsePercentage = totalUsePercentage; + } + +- public int compareTo(Profiler.Result p_compareTo_1_) ++ public int compareTo(Result p_compareTo_1_) + { + if (p_compareTo_1_.usePercentage < this.usePercentage) + { diff --git a/patches/net/minecraft/scoreboard/ScoreboardSaveData.java.patch b/patches/net/minecraft/scoreboard/ScoreboardSaveData.java.patch new file mode 100644 index 00000000..ca7130da --- /dev/null +++ b/patches/net/minecraft/scoreboard/ScoreboardSaveData.java.patch @@ -0,0 +1,10 @@ +--- ../src-base/minecraft/net/minecraft/scoreboard/ScoreboardSaveData.java ++++ ../src-work/minecraft/net/minecraft/scoreboard/ScoreboardSaveData.java +@@ -222,6 +222,7 @@ + for (ScorePlayerTeam scoreplayerteam : this.scoreboard.getTeams()) + { + NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ if (scoreplayerteam.getDisplayName().isEmpty()) continue; // Paper + nbttagcompound.setString("Name", scoreplayerteam.getName()); + nbttagcompound.setString("DisplayName", scoreplayerteam.getDisplayName()); + diff --git a/patches/net/minecraft/scoreboard/ServerScoreboard.java.patch b/patches/net/minecraft/scoreboard/ServerScoreboard.java.patch new file mode 100644 index 00000000..1b53ec77 --- /dev/null +++ b/patches/net/minecraft/scoreboard/ServerScoreboard.java.patch @@ -0,0 +1,127 @@ +--- ../src-base/minecraft/net/minecraft/scoreboard/ServerScoreboard.java ++++ ../src-work/minecraft/net/minecraft/scoreboard/ServerScoreboard.java +@@ -30,7 +30,7 @@ + + if (this.addedObjectives.contains(scoreIn.getObjective())) + { +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketUpdateScore(scoreIn)); ++ this.sendAll(new SPacketUpdateScore(scoreIn)); + } + + this.markSaveDataDirty(); +@@ -39,14 +39,14 @@ + public void broadcastScoreUpdate(String scoreName) + { + super.broadcastScoreUpdate(scoreName); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketUpdateScore(scoreName)); ++ this.sendAll(new SPacketUpdateScore(scoreName)); + this.markSaveDataDirty(); + } + + public void broadcastScoreUpdate(String scoreName, ScoreObjective objective) + { + super.broadcastScoreUpdate(scoreName, objective); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketUpdateScore(scoreName, objective)); ++ this.sendAll(new SPacketUpdateScore(scoreName, objective)); + this.markSaveDataDirty(); + } + +@@ -59,7 +59,7 @@ + { + if (this.getObjectiveDisplaySlotCount(scoreobjective) > 0) + { +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketDisplayObjective(objectiveSlot, objective)); ++ this.sendAll(new SPacketDisplayObjective(objectiveSlot, objective)); + } + else + { +@@ -71,7 +71,7 @@ + { + if (this.addedObjectives.contains(objective)) + { +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketDisplayObjective(objectiveSlot, objective)); ++ this.sendAll(new SPacketDisplayObjective(objectiveSlot, objective)); + } + else + { +@@ -87,7 +87,7 @@ + if (super.addPlayerToTeam(player, newTeam)) + { + ScorePlayerTeam scoreplayerteam = this.getTeam(newTeam); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketTeams(scoreplayerteam, Arrays.asList(player), 3)); ++ this.sendAll(new SPacketTeams(scoreplayerteam, Arrays.asList(player), 3)); + this.markSaveDataDirty(); + return true; + } +@@ -100,7 +100,7 @@ + public void removePlayerFromTeam(String username, ScorePlayerTeam playerTeam) + { + super.removePlayerFromTeam(username, playerTeam); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketTeams(playerTeam, Arrays.asList(username), 4)); ++ this.sendAll(new SPacketTeams(playerTeam, Arrays.asList(username), 4)); + this.markSaveDataDirty(); + } + +@@ -116,7 +116,7 @@ + + if (this.addedObjectives.contains(objective)) + { +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketScoreboardObjective(objective, 2)); ++ this.sendAll(new SPacketScoreboardObjective(objective, 2)); + } + + this.markSaveDataDirty(); +@@ -137,21 +137,21 @@ + public void broadcastTeamCreated(ScorePlayerTeam playerTeam) + { + super.broadcastTeamCreated(playerTeam); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketTeams(playerTeam, 0)); ++ this.sendAll(new SPacketTeams(playerTeam, 0)); + this.markSaveDataDirty(); + } + + public void broadcastTeamInfoUpdate(ScorePlayerTeam playerTeam) + { + super.broadcastTeamInfoUpdate(playerTeam); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketTeams(playerTeam, 2)); ++ this.sendAll(new SPacketTeams(playerTeam, 2)); + this.markSaveDataDirty(); + } + + public void broadcastTeamRemove(ScorePlayerTeam playerTeam) + { + super.broadcastTeamRemove(playerTeam); +- this.scoreboardMCServer.getPlayerList().sendPacketToAllPlayers(new SPacketTeams(playerTeam, 1)); ++ this.sendAll(new SPacketTeams(playerTeam, 1)); + this.markSaveDataDirty(); + } + +@@ -196,6 +196,7 @@ + + for (EntityPlayerMP entityplayermp : this.scoreboardMCServer.getPlayerList().getPlayers()) + { ++ if (entityplayermp.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board + for (Packet packet : list) + { + entityplayermp.connection.sendPacket(packet); +@@ -227,6 +228,7 @@ + + for (EntityPlayerMP entityplayermp : this.scoreboardMCServer.getPlayerList().getPlayers()) + { ++ if (entityplayermp.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board + for (Packet packet : list) + { + entityplayermp.connection.sendPacket(packet); +@@ -250,4 +252,12 @@ + + return i; + } ++ ++ private void sendAll(Packet packet) { ++ for (EntityPlayerMP entityplayer : this.scoreboardMCServer.getPlayerList().getPlayers()) { ++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) { ++ entityplayer.connection.sendPacket(packet); ++ } ++ } ++ } + } diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch new file mode 100644 index 00000000..58155c31 --- /dev/null +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -0,0 +1,2322 @@ +--- ../src-base/minecraft/net/minecraft/server/MinecraftServer.java ++++ ../src-work/minecraft/net/minecraft/server/MinecraftServer.java +@@ -1,7 +1,9 @@ + package net.minecraft.server; + ++import com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue; + import com.google.common.collect.Lists; + import com.google.common.collect.Queues; ++import com.google.common.collect.Sets; + import com.google.common.util.concurrent.Futures; + import com.google.common.util.concurrent.ListenableFuture; + import com.google.common.util.concurrent.ListenableFutureTask; +@@ -17,91 +19,96 @@ + import java.awt.image.BufferedImage; + import java.io.File; + import java.io.IOException; ++import java.io.OutputStream; + import java.io.UnsupportedEncodingException; + import java.net.Proxy; + import java.net.URLEncoder; + import java.nio.charset.StandardCharsets; + import java.security.KeyPair; + import java.text.SimpleDateFormat; +-import java.util.Arrays; +-import java.util.Collections; +-import java.util.Date; +-import java.util.List; +-import java.util.Queue; +-import java.util.Random; +-import java.util.UUID; ++import java.util.*; + import java.util.concurrent.Callable; + import java.util.concurrent.Executors; + import java.util.concurrent.FutureTask; +-import java.util.function.Supplier; + import javax.annotation.Nullable; + import javax.imageio.ImageIO; ++import jline.console.ConsoleReader; ++import joptsimple.OptionSet; + import net.minecraft.advancements.AdvancementManager; + import net.minecraft.advancements.FunctionManager; ++import net.minecraft.block.Block; + import net.minecraft.command.CommandBase; + import net.minecraft.command.ICommandManager; + import net.minecraft.command.ICommandSender; + import net.minecraft.command.ServerCommandManager; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.ICrashReportDetail; ++import net.minecraft.enchantment.Enchantment; + import net.minecraft.entity.Entity; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Bootstrap; ++import net.minecraft.item.Item; + import net.minecraft.network.NetworkSystem; + import net.minecraft.network.ServerStatusResponse; + import net.minecraft.network.play.server.SPacketTimeUpdate; ++import net.minecraft.potion.Potion; + import net.minecraft.profiler.ISnooperInfo; + import net.minecraft.profiler.Profiler; + import net.minecraft.profiler.Snooper; + import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecraft.server.dedicated.PropertyManager; + import net.minecraft.server.management.PlayerList; + import net.minecraft.server.management.PlayerProfileCache; +-import net.minecraft.util.IProgressUpdate; +-import net.minecraft.util.IThreadListener; +-import net.minecraft.util.ITickable; +-import net.minecraft.util.ReportedException; +-import net.minecraft.util.Util; ++import net.minecraft.util.*; + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.DataFixesManager; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentString; +-import net.minecraft.world.EnumDifficulty; +-import net.minecraft.world.GameType; +-import net.minecraft.world.MinecraftException; +-import net.minecraft.world.ServerWorldEventHandler; +-import net.minecraft.world.World; +-import net.minecraft.world.WorldServer; +-import net.minecraft.world.WorldServerDemo; +-import net.minecraft.world.WorldServerMulti; +-import net.minecraft.world.WorldSettings; +-import net.minecraft.world.WorldType; +-import net.minecraft.world.chunk.storage.AnvilSaveConverter; ++import net.minecraft.world.*; ++import net.minecraft.world.chunk.storage.AnvilSaveHandler; + import net.minecraft.world.storage.ISaveFormat; + import net.minecraft.world.storage.ISaveHandler; + import net.minecraft.world.storage.WorldInfo; ++import net.minecraftforge.common.DimensionManager; ++import net.minecraftforge.common.util.EnumHelper; ++import net.minecraftforge.fml.common.FMLCommonHandler; ++import net.minecraftforge.fml.common.registry.EntityRegistry; ++import net.minecraftforge.fml.common.registry.ForgeRegistries; ++import net.minecraftforge.fml.relauncher.ReflectionHelper; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import net.minecraftforge.registries.GameData; + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.Material; ++import org.bukkit.World.Environment; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.Main; ++import org.bukkit.craftbukkit.entity.CraftCustomEntity; ++import org.bukkit.entity.EntityType; ++import org.bukkit.potion.PotionEffectType; ++import org.spigotmc.SlackActivityAccountant; + +-public abstract class MinecraftServer implements ICommandSender, Runnable, IThreadListener, ISnooperInfo +-{ +- private static final Logger LOGGER = LogManager.getLogger(); ++//import jline.console.ConsoleReader; ++ ++public abstract class MinecraftServer implements ICommandSender, Runnable, IThreadListener, ISnooperInfo { ++ public static final Logger LOGGER = LogManager.getLogger(); + public static final File USER_CACHE_FILE = new File("usercache.json"); +- private final ISaveFormat anvilConverterForAnvilFile; ++ public ISaveFormat anvilConverterForAnvilFile; + private final Snooper usageSnooper = new Snooper("server", this, getCurrentTimeMillis()); +- private final File anvilFile; ++ public File anvilFile; + private final List tickables = Lists.newArrayList(); + public final ICommandManager commandManager; + public final Profiler profiler = new Profiler(); + private final NetworkSystem networkSystem; + private final ServerStatusResponse statusResponse = new ServerStatusResponse(); + private final Random random = new Random(); +- private final DataFixer dataFixer; ++ public final DataFixer dataFixer; + @SideOnly(Side.SERVER) + private String hostname; + private int serverPort = -1; +@@ -128,7 +135,6 @@ + private KeyPair serverKeyPair; + private String serverOwner; + private String folderName; +- @SideOnly(Side.CLIENT) + private String worldName; + private boolean isDemo; + private boolean enableBonusChest; +@@ -144,178 +150,264 @@ + private final GameProfileRepository profileRepo; + private final PlayerProfileCache profileCache; + private long nanoTimeSinceStatusRefresh; +- public final Queue < FutureTask> futureTaskQueue = Queues. < FutureTask> newArrayDeque(); ++ public final Queue > futureTaskQueue = new CachedSizeConcurrentLinkedQueue<>(); // Paper - Make size() constant-time + private Thread serverThread; + protected long currentTime = getCurrentTimeMillis(); + @SideOnly(Side.CLIENT) + private boolean worldIconSet; ++ // CraftBukkit start ++ public List worldServerList = new ArrayList<>(); ++ public org.bukkit.craftbukkit.CraftServer server; ++ public OptionSet options; ++ public org.bukkit.command.ConsoleCommandSender console; ++ public org.bukkit.command.RemoteConsoleCommandSender remoteConsole; ++ public ConsoleReader reader; ++ public static int currentTick = (int) (System.currentTimeMillis() / 50); ++ public Thread primaryThread; ++ public Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); ++ public int autosavePeriod; ++ // CraftBukkit end ++ // Spigot start ++ public final SlackActivityAccountant slackActivityAccountant = new SlackActivityAccountant(); ++ // Spigot end + +- public MinecraftServer(File anvilFileIn, Proxy proxyIn, DataFixer dataFixerIn, YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) +- { ++ private static MinecraftServer instance; ++ ++ public MinecraftServer(OptionSet options, Proxy proxyIn, DataFixer dataFixerIn, ++ YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, ++ GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) { ++ instance = this; + this.serverProxy = proxyIn; + this.authService = authServiceIn; + this.sessionService = sessionServiceIn; + this.profileRepo = profileRepoIn; + this.profileCache = profileCacheIn; +- this.anvilFile = anvilFileIn; ++ // this.anvilFile = anvilFileIn; + this.networkSystem = new NetworkSystem(this); + this.commandManager = this.createCommandManager(); +- this.anvilConverterForAnvilFile = new AnvilSaveConverter(anvilFileIn, dataFixerIn); ++ // this.anvilConverterForAnvilFile = new AnvilSaveConverter(anvilFileIn, dataFixerIn); // CraftBukkit - moved to DedicatedServer.init + this.dataFixer = dataFixerIn; ++ this.options = options; ++ // Try to see if we're actually running in a terminal, disable jline if not ++ if (System.console() == null && System.getProperty("jline.terminal") == null) { ++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal"); ++ Main.useJline = false; ++ } ++ try { ++ (this.reader = new ConsoleReader(System.in, (OutputStream)System.out)).setExpandEvents(false); ++ } ++ catch (Throwable e) { ++ try { ++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal"); ++ System.setProperty("user.language", "en"); ++ Main.useJline = false; ++ (this.reader = new ConsoleReader(System.in, (OutputStream)System.out)).setExpandEvents(false); ++ } ++ catch (IOException ex) { ++ MinecraftServer.LOGGER.warn((String)null, (Throwable)ex); ++ } ++ } ++ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this)); ++ this.serverThread = primaryThread = new Thread(net.minecraftforge.fml.common.thread.SidedThreadGroups.SERVER, ++ this, "Server thread"); // Moved from main + } + +- public ServerCommandManager createCommandManager() +- { ++ public abstract PropertyManager getPropertyManager(); ++ ++ public ServerCommandManager createCommandManager() { + return new ServerCommandManager(this); + } + + public abstract boolean init() throws IOException; + +- public void convertMapIfNeeded(String worldNameIn) +- { +- if (this.getActiveAnvilConverter().isOldMapFormat(worldNameIn)) +- { ++ public void convertMapIfNeeded(String worldNameIn) { ++ if (this.getActiveAnvilConverter().isOldMapFormat(worldNameIn)) { + LOGGER.info("Converting map!"); + this.setUserMessage("menu.convertingLevel"); +- this.getActiveAnvilConverter().convertMapFormat(worldNameIn, new IProgressUpdate() +- { ++ this.getActiveAnvilConverter().convertMapFormat(worldNameIn, new IProgressUpdate() { + private long startTime = System.currentTimeMillis(); +- public void displaySavingString(String message) +- { ++ ++ public void displaySavingString(String message) { + } +- public void setLoadingProgress(int progress) +- { +- if (System.currentTimeMillis() - this.startTime >= 1000L) +- { ++ ++ public void setLoadingProgress(int progress) { ++ if (System.currentTimeMillis() - this.startTime >= 1000L) { + this.startTime = System.currentTimeMillis(); +- MinecraftServer.LOGGER.info("Converting... {}%", (int)progress); ++ MinecraftServer.LOGGER.info("Converting... {}%", (int) progress); + } + } ++ + @SideOnly(Side.CLIENT) +- public void resetProgressAndMessage(String message) +- { ++ public void resetProgressAndMessage(String message) { + } ++ + @SideOnly(Side.CLIENT) +- public void setDoneWorking() +- { ++ public void setDoneWorking() { + } +- public void displayLoadingString(String message) +- { ++ ++ public void displayLoadingString(String message) { + } + }); + } + } + +- protected synchronized void setUserMessage(String message) +- { ++ protected synchronized void setUserMessage(String message) { + this.userMessage = message; + } + + @Nullable + @SideOnly(Side.CLIENT) + +- public synchronized String getUserMessage() +- { ++ public synchronized String getUserMessage() { + return this.userMessage; + } + +- public void loadAllWorlds(String saveName, String worldNameIn, long seed, WorldType type, String generatorOptions) +- { ++ public void loadAllWorlds(String saveName, String worldNameIn, long seed, WorldType type, String generatorOptions) { ++ ServerCommandManager vanillaCommandManager = (ServerCommandManager) this.getCommandManager(); ++ vanillaCommandManager.registerVanillaCommands(); + this.convertMapIfNeeded(saveName); + this.setUserMessage("menu.loadingLevel"); +- ISaveHandler isavehandler = this.anvilConverterForAnvilFile.getSaveLoader(saveName, true); +- this.setResourcePackFromWorld(this.getFolderName(), isavehandler); +- WorldInfo worldinfo = isavehandler.loadWorldInfo(); +- WorldSettings worldsettings; ++ this.worlds = new WorldServer[3]; + +- if (worldinfo == null) +- { +- if (this.isDemo()) +- { +- worldsettings = WorldServerDemo.DEMO_WORLD_SETTINGS; +- } +- else +- { +- worldsettings = new WorldSettings(seed, this.getGameType(), this.canStructuresSpawn(), this.isHardcore(), type); +- worldsettings.setGeneratorOptions(generatorOptions); ++ WorldSettings worldsettings = new WorldSettings(seed, this.getGameType(), this.canStructuresSpawn(), this.isHardcore(), type); ++ worldsettings.setGeneratorOptions(generatorOptions); ++ WorldServer world; + +- if (this.enableBonusChest) +- { +- worldsettings.enableBonusChest(); ++ // WorldServer overWorld = (WorldServer)(isDemo() ? new WorldServerDemo(this, new AnvilSaveHandler(server.getWorldContainer(), worldNameIn , true, this.dataFixer), worldinfo, 0, profiler).init() : new WorldServer(this, new AnvilSaveHandler(server.getWorldContainer(), worldNameIn , true, this.dataFixer), worldinfo, 0, profiler).init()); ++ Integer[] dimIds = net.minecraftforge.common.DimensionManager.getStaticDimensionIDs(); ++ Arrays.sort(dimIds, new Comparator() { ++ @Override ++ public int compare(Integer o1, Integer o2) { ++ // Zero-dimension must always be the first in array! ++ if (o1 == 0) { ++ return -1; ++ } else { ++ return Math.max(o1, o2); + } + } +- +- worldinfo = new WorldInfo(worldsettings, worldNameIn); +- } +- else ++ }); ++ for (int dim : dimIds) + { +- worldinfo.setWorldName(worldNameIn); +- worldsettings = new WorldSettings(worldinfo); +- } +- +- if (false) { //Forge Dead code, reimplemented below +- for (int i = 0; i < this.worlds.length; ++i) +- { +- int j = 0; +- +- if (i == 1) +- { +- j = -1; ++ // World validation ++ if (dim != 0) { ++ if ((dim == -1 && !this.getAllowNether()) || (dim == 1 && !server.getAllowEnd())) { ++ continue; ++ } + } + +- if (i == 2) +- { +- j = 1; ++ String worldType; ++ org.bukkit.World.Environment worldEnvironment = org.bukkit.World.Environment.getEnvironment(dim); ++ if (worldEnvironment == null) { ++ WorldProvider provider = DimensionManager.createProviderFor(dim); ++ worldType = provider.getClass().getSimpleName().toLowerCase(); ++ worldType = worldType.replace("worldprovider", ""); ++ worldType = worldType.replace("provider", ""); ++ worldEnvironment = Environment.getEnvironment(DimensionManager.getProviderType(dim).getId()); ++ } else { ++ worldType = worldEnvironment.toString().toLowerCase(); + } ++ String name = (dim == 0) ? saveName : "DIM" + dim; ++ org.bukkit.generator.ChunkGenerator gen = null; + +- if (i == 0) +- { +- if (this.isDemo()) +- { +- this.worlds[i] = (WorldServer)(new WorldServerDemo(this, isavehandler, worldinfo, j, this.profiler)).init(); ++ if (dim == 0) { ++ ISaveHandler idatamanager = new AnvilSaveHandler(server.getWorldContainer(), worldNameIn, true, this.dataFixer); ++ WorldInfo worlddata = idatamanager.loadWorldInfo(); ++ // Kettle start ++ for (Map.Entry entry : ForgeRegistries.ITEMS.getEntries()) { ++ ResourceLocation key = entry.getKey(); ++ Item item = entry.getValue(); ++ if(!key.getResourceDomain().equals("minecraft")) { ++ String materialName = key.toString().toUpperCase().replaceAll("(:|\\s)", "_").replaceAll("\\W", ""); ++ Material material = Material.addMaterial(EnumHelper.addEnum(Material.class, materialName, new Class[]{Integer.TYPE, Integer.TYPE}, new Object[]{Item.getIdFromItem(item), item.getItemStackLimit()})); ++ if (material != null) { ++ MinecraftServer.LOGGER.info(String.format("Injected new Forge item material %s with ID %d.", material.name(), material.getId())); ++ } else { ++ MinecraftServer.LOGGER.info(String.format("Inject item failure %s with ID %d.", materialName, Item.getIdFromItem(item))); ++ } ++ } + } +- else +- { +- this.worlds[i] = (WorldServer)(new WorldServer(this, isavehandler, worldinfo, j, this.profiler)).init(); ++ for (Material material : Material.values()) { ++ if (material.getId() < 256) ++ Material.addBlockMaterial(material); + } ++ for (Map.Entry entry : ForgeRegistries.BLOCKS.getEntries()) { ++ ResourceLocation key = entry.getKey(); ++ Block block = entry.getValue(); ++ if(!key.getResourceDomain().equals("minecraft")) { ++ String materialName = key.toString().toUpperCase().replaceAll("(:|\\s)", "_").replaceAll("\\W", ""); ++ Material material = Material.addBlockMaterial(EnumHelper.addEnum(Material.class, materialName, new Class[]{Integer.TYPE}, new Object[]{Block.getIdFromBlock(block)})); ++ if (material != null) { ++ MinecraftServer.LOGGER.info(String.format("Injected new Forge block material %s with ID %d.", material.name(), material.getId())); ++ } else { ++ MinecraftServer.LOGGER.info(String.format("Inject block failure %s with ID %d.", materialName, Block.getIdFromBlock(block))); ++ } ++ } ++ } ++ Map NAME_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "NAME_MAP"); ++ Map ID_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "ID_MAP"); + +- this.worlds[i].initialize(worldsettings); +- } +- else +- { +- this.worlds[i] = (WorldServer)(new WorldServerMulti(this, isavehandler, j, this.worlds[0], this.profiler)).init(); +- } ++ for (Map.Entry> entity : EntityRegistry.entityClassMap.entrySet()) { ++ String entityname = entity.getKey(); ++ String entityType = entityname.replace("-", "_").toUpperCase(); + +- this.worlds[i].addEventListener(new ServerWorldEventHandler(this, this.worlds[i])); ++ int typeId = GameData.getEntityRegistry().getID(EntityRegistry.getEntry(entity.getValue())); ++ EntityType bukkitType = EnumHelper.addEnum(EntityType.class, entityType, new Class[] { String.class, Class.class, Integer.TYPE, Boolean.TYPE }, new Object[] { entityname, CraftCustomEntity.class, typeId, false }); + +- if (!this.isSinglePlayer()) +- { +- this.worlds[i].getWorldInfo().setGameType(this.getGameType()); +- } +- } +- } //Forge: End dead code ++ NAME_MAP.put(entityname.toLowerCase(), bukkitType); ++ ID_MAP.put((short)typeId, bukkitType); ++ } ++ for (Object enchantment : Enchantment.REGISTRY) { ++ org.bukkit.enchantments.Enchantment.registerEnchantment(new org.bukkit.craftbukkit.enchantments.CraftEnchantment((Enchantment) enchantment)); ++ } ++ org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations(); ++ for (Object effect : Potion.REGISTRY) { ++ PotionEffectType.registerPotionEffectType(new org.bukkit.craftbukkit.potion.CraftPotionEffectType((Potion) effect)); ++ } ++ PotionEffectType.stopAcceptingRegistrations(); ++ server.loadPlugins(); ++ server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); ++ // Kettle end ++ if (worlddata == null) { ++ worlddata = new WorldInfo(worldsettings, worldNameIn); ++ } ++ worlddata.checkName(worldNameIn); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end) ++ if (this.isDemo()) { ++ world = (WorldServer) (new WorldServerDemo(this, idatamanager, worlddata, dim, this.profiler)).init(); ++ } else { ++ world = (WorldServer) (new WorldServer(this, idatamanager, worlddata, dim, this.profiler, worldEnvironment, gen)).init(); ++ } + +- WorldServer overWorld = (WorldServer)(isDemo() ? new WorldServerDemo(this, isavehandler, worldinfo, 0, profiler).init() : new WorldServer(this, isavehandler, worldinfo, 0, profiler).init()); +- overWorld.initialize(worldsettings); +- for (int dim : net.minecraftforge.common.DimensionManager.getStaticDimensionIDs()) +- { +- WorldServer world = (dim == 0 ? overWorld : (WorldServer)new WorldServerMulti(this, isavehandler, dim, overWorld, profiler).init()); ++ world.initialize(worldsettings); ++ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard()); ++ } else { ++ gen = this.server.getGenerator(name); ++ ++ ISaveHandler idatamanager = new AnvilSaveHandler(server.getWorldContainer(), name, true, this.dataFixer); ++ // world =, b0 to dimension, s1 to name, added Environment and gen ++ WorldInfo worlddata = idatamanager.loadWorldInfo(); ++ if (worlddata == null) { ++ worlddata = new WorldInfo(worldsettings, name); ++ } ++ worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end) ++ world = (WorldServer) new WorldServerMulti(this, idatamanager, dim, this.worlds[0], this.profiler, worlddata, worldEnvironment, gen).init(); ++ } ++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(world.getWorld())); + world.addEventListener(new ServerWorldEventHandler(this, world)); + + if (!this.isSinglePlayer()) + { + world.getWorldInfo().setGameType(this.getGameType()); + } ++ getPlayerList().setPlayerManager(worldServerList.toArray(new WorldServer[worldServerList.size()])); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.WorldEvent.Load(world)); + } + +- this.playerList.setPlayerManager(new WorldServer[]{ overWorld }); ++ this.playerList.setPlayerManager(this.worlds); + this.setDifficultyForAllWorlds(this.getDifficulty()); + this.initialWorldChunkLoad(); + } + +- public void initialWorldChunkLoad() +- { ++ public void initialWorldChunkLoad() { + int i = 16; + int j = 4; + int k = 192; +@@ -323,45 +415,52 @@ + int i1 = 0; + this.setUserMessage("menu.generatingTerrain"); + int j1 = 0; +- LOGGER.info("Preparing start region for level 0"); +- WorldServer worldserver = net.minecraftforge.common.DimensionManager.getWorld(j1); +- BlockPos blockpos = worldserver.getSpawnPoint(); +- long k1 = getCurrentTimeMillis(); ++ // CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory ++ for (int m = 0; m < worldServerList.size(); m++) { ++ WorldServer worldserver = this.worldServerList.get(m); ++ MinecraftServer.LOGGER ++ .info("Preparing start region for level " + m + " (Seed: " + worldserver.getSeed() + ")"); + +- for (int l1 = -192; l1 <= 192 && this.isServerRunning(); l1 += 16) +- { +- for (int i2 = -192; i2 <= 192 && this.isServerRunning(); i2 += 16) +- { +- long j2 = getCurrentTimeMillis(); ++ if (!worldserver.getWorld().getKeepSpawnInMemory()) { ++ continue; ++ } + +- if (j2 - k1 > 1000L) +- { +- this.outputPercentRemaining("Preparing spawn area", i1 * 100 / 625); +- k1 = j2; +- } ++ BlockPos blockposition = worldserver.getSpawnPoint(); ++ long jk = getCurrentTimeMillis(); ++ i = 0; + +- ++i1; +- worldserver.getChunkProvider().provideChunk(blockpos.getX() + l1 >> 4, blockpos.getZ() + i2 >> 4); ++ for (int l1 = -192; l1 <= 192 && this.isServerRunning(); l1 += 16) { ++ for (int i2 = -192; i2 <= 192 && this.isServerRunning(); i2 += 16) { ++ long j2 = getCurrentTimeMillis(); ++ ++ if (j2 - jk > 1000L) { ++ this.outputPercentRemaining("Preparing spawn area", i * 100 / 625); ++ jk = j2; ++ } ++ ++i; ++ worldserver.getChunkProvider().provideChunk(blockposition.getX() + l1 >> 4, ++ blockposition.getZ() + i2 >> 4); ++ } + } + } + ++ for (WorldServer world : this.worldServerList) { ++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(world.getWorld())); ++ } ++ + this.clearCurrentTask(); + } + +- public void setResourcePackFromWorld(String worldNameIn, ISaveHandler saveHandlerIn) +- { ++ public void setResourcePackFromWorld(String worldNameIn, ISaveHandler saveHandlerIn) { + File file1 = new File(saveHandlerIn.getWorldDirectory(), "resources.zip"); + +- if (file1.isFile()) +- { +- try +- { +- this.setResourcePack("level://" + URLEncoder.encode(worldNameIn, StandardCharsets.UTF_8.toString()) + "/" + "resources.zip", ""); ++ if (file1.isFile()) { ++ try { ++ this.setResourcePack("level://" + URLEncoder.encode(worldNameIn, StandardCharsets.UTF_8.toString()) ++ + "/" + "resources.zip", ""); ++ } catch (UnsupportedEncodingException var5) { ++ LOGGER.warn("Something went wrong url encoding {}", (Object) worldNameIn); + } +- catch (UnsupportedEncodingException var5) +- { +- LOGGER.warn("Something went wrong url encoding {}", (Object)worldNameIn); +- } + } + } + +@@ -379,73 +478,79 @@ + + public abstract boolean shouldBroadcastConsoleToOps(); + +- protected void outputPercentRemaining(String message, int percent) +- { ++ protected void outputPercentRemaining(String message, int percent) { + this.currentTask = message; + this.percentDone = percent; + LOGGER.info("{}: {}%", message, Integer.valueOf(percent)); + } + +- protected void clearCurrentTask() +- { ++ protected void clearCurrentTask() { + this.currentTask = null; + this.percentDone = 0; ++ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); + } + +- public void saveAllWorlds(boolean isSilent) +- { +- for (WorldServer worldserver : this.worlds) +- { +- if (worldserver != null) +- { +- if (!isSilent) +- { +- LOGGER.info("Saving chunks for level '{}'/{}", worldserver.getWorldInfo().getWorldName(), worldserver.provider.getDimensionType().getName()); ++ public void saveAllWorlds(boolean isSilent) { ++ for (WorldServer worldserver : this.worldServerList) { ++ if (worldserver != null) { ++ if (!isSilent) { ++ LOGGER.info("Saving chunks for level '{}'/{}", worldserver.getWorldInfo().getWorldName(), ++ worldserver.provider.getDimensionType().getName()); + } + +- try +- { +- worldserver.saveAllChunks(true, (IProgressUpdate)null); +- } +- catch (MinecraftException minecraftexception) +- { ++ try { ++ worldserver.saveAllChunks(true, (IProgressUpdate) null); ++ } catch (MinecraftException minecraftexception) { + LOGGER.warn(minecraftexception.getMessage()); + } + } + } + } + +- public void stopServer() ++ private boolean hasStopped = false; ++ private final Object stopLock = new Object(); ++ ++ public void stopServer() throws MinecraftException + { ++ org.spigotmc.AsyncCatcher.enabled = false; // Spigot ++ // CraftBukkit start - prevent double stopping on multiple threads ++ synchronized (stopLock) { ++ if (hasStopped) ++ return; ++ hasStopped = true; ++ } ++ // CraftBukkit end + LOGGER.info("Stopping server"); +- +- if (this.getNetworkSystem() != null) +- { ++ if (this.server != null) { ++ this.server.disablePlugins(); ++ } ++ if (this.getNetworkSystem() != null) { + this.getNetworkSystem().terminateEndpoints(); + } + +- if (this.playerList != null) +- { ++ if (this.playerList != null) { + LOGGER.info("Saving players"); + this.playerList.saveAllPlayerData(); + this.playerList.removeAllPlayers(); ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException ex) { ++ } // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets + } + +- if (this.worlds != null) +- { ++ if (this.worlds != null) { + LOGGER.info("Saving worlds"); + +- for (WorldServer worldserver : this.worlds) +- { +- if (worldserver != null) +- { ++ for (WorldServer worldserver : this.worlds) { ++ if (worldserver != null) { + worldserver.disableLevelSaving = false; + } + } + + this.saveAllWorlds(false); + +- for (WorldServer worldserver1 : this.worlds) ++ // CraftBukkit start - Handled in saveChunks ++ for (WorldServer worldserver1 : this.worldServerList) + { + if (worldserver1 != null) + { +@@ -453,159 +558,213 @@ + worldserver1.flush(); + } + } ++ // CraftBukkit end + + WorldServer[] tmp = worlds; +- for (WorldServer world : tmp) +- { ++ for (WorldServer world : tmp) { + net.minecraftforge.common.DimensionManager.setWorld(world.provider.getDimension(), null, this); + } + } + +- if (this.usageSnooper.isSnooperRunning()) +- { ++ if (this.usageSnooper.isSnooperRunning()) { + this.usageSnooper.stopSnooper(); + } + + CommandBase.setCommandListener(null); // Forge: fix MC-128561 ++ // Spigot start ++ if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { ++ Object[] p = {"usercache.json"}; ++ LOGGER.info("Saving usercache.json"); ++ this.profileCache.save(); ++ } ++ // Spigot end + } + +- public boolean isServerRunning() +- { ++ public boolean isServerRunning() { + return this.serverRunning; + } + +- public void initiateShutdown() +- { ++ public void initiateShutdown() { + this.serverRunning = false; + } + ++ private static final int TPS = 20; ++ private static final long SEC_IN_NANO = 1000000000; ++ public static final long TICK_TIME = SEC_IN_NANO / TPS; ++ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L; ++ private static final int SAMPLE_INTERVAL = 20; ++ public final RollingAverage tps1 = new RollingAverage(60); ++ public final RollingAverage tps5 = new RollingAverage(60 * 5); ++ public final RollingAverage tps15 = new RollingAverage(60 * 15); ++ public double[] recentTps = new double[3]; ++ ++ public static class RollingAverage { ++ private final int size; ++ private long time; ++ private double total; ++ private int index = 0; ++ private final double[] samples; ++ private final long[] times; ++ ++ RollingAverage(int size) { ++ this.size = size; ++ this.time = size * SEC_IN_NANO; ++ this.total = TPS * SEC_IN_NANO * size; ++ this.samples = new double[size]; ++ this.times = new long[size]; ++ for (int i = 0; i < size; i++) { ++ this.samples[i] = TPS; ++ this.times[i] = SEC_IN_NANO; ++ } ++ } ++ ++ public void add(double x, long t) { ++ time -= times[index]; ++ total -= samples[index] * times[index]; ++ samples[index] = x; ++ times[index] = t; ++ time += t; ++ total += x * t; ++ if (++index == size) { ++ index = 0; ++ } ++ } ++ ++ public double getAverage() { ++ return total / time; ++ } ++ } ++ + public void run() + { + try + { + if (this.init()) + { +- net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStarted(); ++ FMLCommonHandler.instance().handleServerStarted(); + this.currentTime = getCurrentTimeMillis(); + long i = 0L; + this.statusResponse.setServerDescription(new TextComponentString(this.motd)); + this.statusResponse.setVersion(new ServerStatusResponse.Version("1.12.2", 340)); + this.applyServerIconToResponse(this.statusResponse); + ++ // Spigot start ++ Arrays.fill( recentTps, 20 ); ++ long start = System.nanoTime(), lastTick = start - TICK_TIME, catchupTime = 0, curTime, wait, tickSection = start; + while (this.serverRunning) + { +- long k = getCurrentTimeMillis(); +- long j = k - this.currentTime; +- +- if (j > 2000L && this.currentTime - this.timeOfLastWarning >= 15000L) +- { +- LOGGER.warn("Can't keep up! Did the system time change, or is the server overloaded? Running {}ms behind, skipping {} tick(s)", Long.valueOf(j), Long.valueOf(j / 50L)); +- j = 2000L; +- this.timeOfLastWarning = this.currentTime; ++ curTime = System.nanoTime(); ++ wait = TICK_TIME - (curTime - lastTick); ++ if (wait > 0) { ++ if (catchupTime < 2E6) { ++ wait += Math.abs(catchupTime); ++ } else if (wait < catchupTime) { ++ catchupTime -= wait; ++ wait = 0; ++ } else { ++ wait -= catchupTime; ++ catchupTime = 0; ++ } + } +- +- if (j < 0L) +- { +- LOGGER.warn("Time ran backwards! Did the system time change?"); +- j = 0L; ++ if (wait > 0) { ++ Thread.sleep(wait / 1000000); ++ curTime = System.nanoTime(); ++ wait = TICK_TIME - (curTime - lastTick); + } + +- i += j; +- this.currentTime = k; +- +- if (this.worlds[0].areAllPlayersAsleep()) ++ catchupTime = Math.min(MAX_CATCHUP_BUFFER, catchupTime - wait); ++ if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 ) + { +- this.tick(); +- i = 0L; ++ final long diff = curTime - tickSection; ++ double currentTps = 1E9 / diff * SAMPLE_INTERVAL; ++ tps1.add(currentTps, diff); ++ tps5.add(currentTps, diff); ++ tps15.add(currentTps, diff); ++ // Backwards compat with bad plugins ++ recentTps[0] = tps1.getAverage(); ++ recentTps[1] = tps5.getAverage(); ++ recentTps[2] = tps15.getAverage(); ++ tickSection = curTime; + } +- else +- { +- while (i > 50L) +- { +- i -= 50L; +- this.tick(); +- } +- } ++ lastTick = curTime; + +- Thread.sleep(Math.max(1L, 50L - i)); ++ this.tick(); + this.serverIsRunning = true; + } +- net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStopping(); +- net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions ++ // Spigot end ++ FMLCommonHandler.instance().handleServerStopping(); ++ FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + } + else + { +- net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions +- this.finalTick((CrashReport)null); ++ FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions ++ this.finalTick((CrashReport) null); + } +- } +- catch (net.minecraftforge.fml.common.StartupQuery.AbortedException e) +- { ++ } catch (net.minecraftforge.fml.common.StartupQuery.AbortedException e) { + // ignore silently +- net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions ++ FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + } + catch (Throwable throwable1) + { + LOGGER.error("Encountered an unexpected exception", throwable1); + CrashReport crashreport = null; + +- if (throwable1 instanceof ReportedException) +- { +- crashreport = this.addServerInfoToCrashReport(((ReportedException)throwable1).getCrashReport()); +- } +- else +- { ++ if (throwable1 instanceof ReportedException) { ++ crashreport = this.addServerInfoToCrashReport(((ReportedException) throwable1).getCrashReport()); ++ } else { + crashreport = this.addServerInfoToCrashReport(new CrashReport("Exception in server tick loop", throwable1)); + } + +- File file1 = new File(new File(this.getDataDirectory(), "crash-reports"), "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt"); ++ File file1 = new File(new File(this.getDataDirectory(), "crash-reports"), ++ "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt"); + +- if (crashreport.saveToFile(file1)) +- { +- LOGGER.error("This crash report has been saved to: {}", (Object)file1.getAbsolutePath()); ++ if (crashreport.saveToFile(file1)) { ++ LOGGER.error(String.format("This crash report has been saved to: {0}", new Object[] { file1.getAbsolutePath()})); + } + else + { + LOGGER.error("We were unable to save this crash report to disk."); + } + +- net.minecraftforge.fml.common.FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions ++ FMLCommonHandler.instance().expectServerStopped(); // has to come before finalTick to avoid race conditions + this.finalTick(crashreport); +- } +- finally +- { +- try +- { ++ } finally { ++ try { + this.stopServer(); +- } +- catch (Throwable throwable) +- { ++ } catch (Throwable throwable) { + LOGGER.error("Exception stopping the server", throwable); +- } ++ FMLCommonHandler.instance().handleServerStopped(); ++ this.serverStopped = true; ++ try { ++ this.reader.getTerminal().restore(); ++ } ++ catch (Exception ex) {} ++ this.systemExitNow(); ++ } + finally + { +- net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStopped(); ++ FMLCommonHandler.instance().handleServerStopped(); + this.serverStopped = true; ++ try { ++ this.reader.getTerminal().restore(); ++ } ++ catch (Exception ex2) {} + this.systemExitNow(); + } + } + } + +- public void applyServerIconToResponse(ServerStatusResponse response) +- { ++ public void applyServerIconToResponse(ServerStatusResponse response) { + File file1 = this.getFile("server-icon.png"); + +- if (!file1.exists()) +- { ++ if (!file1.exists()) { + file1 = this.getActiveAnvilConverter().getFile(this.getFolderName(), "icon.png"); + } + +- if (file1.isFile()) +- { ++ if (file1.isFile()) { + ByteBuf bytebuf = Unpooled.buffer(); + +- try +- { ++ try { + BufferedImage bufferedimage = ImageIO.read(file1); + Validate.validState(bufferedimage.getWidth() == 64, "Must be 64 pixels wide"); + Validate.validState(bufferedimage.getHeight() == 64, "Must be 64 pixels high"); +@@ -613,52 +772,42 @@ + ByteBuf bytebuf1 = Base64.encode(bytebuf); + response.setFavicon("data:image/png;base64," + bytebuf1.toString(StandardCharsets.UTF_8)); + bytebuf1.release(); // Forge: fix MC-122085 +- } +- catch (Exception exception) +- { +- LOGGER.error("Couldn't load server icon", (Throwable)exception); +- } +- finally +- { ++ } catch (Exception exception) { ++ LOGGER.error("Couldn't load server icon", (Throwable) exception); ++ } finally { + bytebuf.release(); + } + } + } + + @SideOnly(Side.CLIENT) +- public boolean isWorldIconSet() +- { ++ public boolean isWorldIconSet() { + this.worldIconSet = this.worldIconSet || this.getWorldIconFile().isFile(); + return this.worldIconSet; + } + + @SideOnly(Side.CLIENT) +- public File getWorldIconFile() +- { ++ public File getWorldIconFile() { + return this.getActiveAnvilConverter().getFile(this.getFolderName(), "icon.png"); + } + +- public File getDataDirectory() +- { ++ public File getDataDirectory() { + return new File("."); + } + +- public void finalTick(CrashReport report) +- { ++ public void finalTick(CrashReport report) { + } + +- public void systemExitNow() +- { ++ public void systemExitNow() { + } + + public void tick() + { + long i = System.nanoTime(); +- net.minecraftforge.fml.common.FMLCommonHandler.instance().onPreServerTick(); ++ FMLCommonHandler.instance().onPreServerTick(); + ++this.tickCounter; + +- if (this.startProfiling) +- { ++ if (this.startProfiling) { + this.startProfiling = false; + this.profiler.profilingEnabled = true; + this.profiler.clearProfiling(); +@@ -667,16 +816,15 @@ + this.profiler.startSection("root"); + this.updateTimeLightAndEntities(); + +- if (i - this.nanoTimeSinceStatusRefresh >= 5000000000L) +- { ++ if (i - this.nanoTimeSinceStatusRefresh >= 5000000000L) { + this.nanoTimeSinceStatusRefresh = i; +- this.statusResponse.setPlayers(new ServerStatusResponse.Players(this.getMaxPlayers(), this.getCurrentPlayerCount())); ++ this.statusResponse ++ .setPlayers(new ServerStatusResponse.Players(this.getMaxPlayers(), this.getCurrentPlayerCount())); + GameProfile[] agameprofile = new GameProfile[Math.min(this.getCurrentPlayerCount(), 12)]; + int j = MathHelper.getInt(this.random, 0, this.getCurrentPlayerCount() - agameprofile.length); + +- for (int k = 0; k < agameprofile.length; ++k) +- { +- agameprofile[k] = ((EntityPlayerMP)this.playerList.getPlayers().get(j + k)).getGameProfile(); ++ for (int k = 0; k < agameprofile.length; ++k) { ++ agameprofile[k] = ((EntityPlayerMP) this.playerList.getPlayers().get(j + k)).getGameProfile(); + } + + Collections.shuffle(Arrays.asList(agameprofile)); +@@ -684,7 +832,7 @@ + this.statusResponse.invalidateJson(); + } + +- if (this.tickCounter % 900 == 0) ++ if (autosavePeriod > 0 && this.tickCounter % autosavePeriod == 0) // CraftBukkit + { + this.profiler.startSection("save"); + this.playerList.saveAllPlayerData(); +@@ -697,95 +845,100 @@ + this.profiler.endSection(); + this.profiler.startSection("snooper"); + +- if (!this.usageSnooper.isSnooperRunning() && this.tickCounter > 100) +- { ++ if (!this.usageSnooper.isSnooperRunning() && this.tickCounter > 100) { + this.usageSnooper.startSnooper(); + } + +- if (this.tickCounter % 6000 == 0) +- { ++ if (this.tickCounter % 6000 == 0) { + this.usageSnooper.addMemoryStatsToSnooper(); + } + + this.profiler.endSection(); + this.profiler.endSection(); +- net.minecraftforge.fml.common.FMLCommonHandler.instance().onPostServerTick(); ++ FMLCommonHandler.instance().onPostServerTick(); + } + + public void updateTimeLightAndEntities() + { ++ this.server.getScheduler().mainThreadHeartbeat(this.tickCounter); // CraftBukkit + this.profiler.startSection("jobs"); + +- synchronized (this.futureTaskQueue) +- { +- while (!this.futureTaskQueue.isEmpty()) +- { +- Util.runTask(this.futureTaskQueue.poll(), LOGGER); ++ // Spigot start ++ FutureTask entry; ++ int count = this.futureTaskQueue.size(); ++ while (count-- > 0 && (entry = this.futureTaskQueue.poll()) != null) { ++ Util.runTask(entry, MinecraftServer.LOGGER); + } +- } ++ // Spigot end + + this.profiler.endStartSection("levels"); ++ // CraftBukkit start ++ // Run tasks that are waiting on processing ++ while (!processQueue.isEmpty()) { ++ processQueue.remove().run(); ++ } ++ ++ org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.tick(); ++ ++ // Send time updates to everyone, it will get the right time from the world the player is in. ++ if (this.tickCounter % 20 == 0) { ++ for (int i = 0; i < this.getPlayerList().getPlayers().size(); ++i) { ++ EntityPlayerMP entityplayer = (EntityPlayerMP) this.getPlayerList().getPlayers().get(i); ++ entityplayer.connection.sendPacket(new SPacketTimeUpdate(entityplayer.world.getTotalWorldTime(), ++ entityplayer.getPlayerTime(), entityplayer.world.getGameRules().getBoolean("doDaylightCycle"))); // Add support for per player time ++ } ++ } + net.minecraftforge.common.chunkio.ChunkIOExecutor.tick(); + +- Integer[] ids = net.minecraftforge.common.DimensionManager.getIDs(this.tickCounter % 200 == 0); +- for (int x = 0; x < ids.length; x++) ++ for (int x = 0; x < worldServerList.size(); x++) + { +- int id = ids[x]; + long i = System.nanoTime(); + +- if (id == 0 || this.getAllowNether()) ++ WorldServer worldserver = worldServerList.get(x); ++ int id = worldserver.dimension; ++ ++ this.profiler.func_194340_a(() -> + { +- WorldServer worldserver = net.minecraftforge.common.DimensionManager.getWorld(id); +- this.profiler.func_194340_a(() -> +- { +- return worldserver.getWorldInfo().getWorldName(); +- }); ++ return worldserver.getWorldInfo().getWorldName(); ++ }); + +- if (this.tickCounter % 20 == 0) +- { +- this.profiler.startSection("timeSync"); +- this.playerList.sendPacketToAllPlayersInDimension(new SPacketTimeUpdate(worldserver.getTotalWorldTime(), worldserver.getWorldTime(), worldserver.getGameRules().getBoolean("doDaylightCycle")), worldserver.provider.getDimension()); +- this.profiler.endSection(); +- } ++ this.profiler.startSection("tick"); ++ net.minecraftforge.fml.common.FMLCommonHandler.instance().onPreWorldTick(worldserver); + +- this.profiler.startSection("tick"); +- net.minecraftforge.fml.common.FMLCommonHandler.instance().onPreWorldTick(worldserver); +- + try + { +- worldserver.tick(); ++ worldserver.tick(); + } + catch (Throwable throwable1) + { +- CrashReport crashreport = CrashReport.makeCrashReport(throwable1, "Exception ticking world"); +- worldserver.addWorldInfoToCrashReport(crashreport); +- throw new ReportedException(crashreport); +- } ++ CrashReport crashreport = CrashReport.makeCrashReport(throwable1, "Exception ticking world"); ++ worldserver.addWorldInfoToCrashReport(crashreport); ++ throw new ReportedException(crashreport); ++ } + + try + { +- worldserver.updateEntities(); ++ worldserver.updateEntities(); + } + catch (Throwable throwable) + { +- CrashReport crashreport1 = CrashReport.makeCrashReport(throwable, "Exception ticking world entities"); +- worldserver.addWorldInfoToCrashReport(crashreport1); +- throw new ReportedException(crashreport1); +- } +- +- net.minecraftforge.fml.common.FMLCommonHandler.instance().onPostWorldTick(worldserver); +- this.profiler.endSection(); +- this.profiler.startSection("tracker"); +- worldserver.getEntityTracker().tick(); +- this.profiler.endSection(); +- this.profiler.endSection(); ++ CrashReport crashreport1 = CrashReport.makeCrashReport(throwable, "Exception ticking world entities"); ++ worldserver.addWorldInfoToCrashReport(crashreport1); ++ throw new ReportedException(crashreport1); + } + +- worldTickTimes.get(id)[this.tickCounter % 100] = System.nanoTime() - i; ++ net.minecraftforge.fml.common.FMLCommonHandler.instance().onPostWorldTick(worldserver); ++ this.profiler.endSection(); ++ this.profiler.startSection("tracker"); ++ worldserver.getEntityTracker().tick(); ++ this.profiler.endSection(); ++ this.profiler.endSection(); ++ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions ++ ++ worldTickTimes.get(id)[this.tickCounter % 100] = System.nanoTime() - i; + } + + this.profiler.endStartSection("dim_unloading"); +- net.minecraftforge.common.DimensionManager.unloadWorlds(worldTickTimes); + this.profiler.endStartSection("connection"); + this.getNetworkSystem().networkTick(); + this.profiler.endStartSection("players"); +@@ -794,33 +947,30 @@ + this.getFunctionManager().update(); + this.profiler.endStartSection("tickables"); + +- for (int k = 0; k < this.tickables.size(); ++k) +- { +- ((ITickable)this.tickables.get(k)).update(); ++ for (int k = 0; k < this.tickables.size(); ++k) { ++ ((ITickable) this.tickables.get(k)).update(); + } + + this.profiler.endSection(); + } + +- public boolean getAllowNether() +- { ++ public boolean getAllowNether() { + return true; + } + +- public void startServerThread() +- { ++ public void startServerThread() { ++ /* CraftBukkit start - prevent abuse + net.minecraftforge.fml.common.StartupQuery.reset(); + this.serverThread = new Thread(net.minecraftforge.fml.common.thread.SidedThreadGroups.SERVER, this, "Server thread"); + this.serverThread.start(); ++ // CraftBukkit end */ + } + +- public File getFile(String fileName) +- { ++ public File getFile(String fileName) { + return new File(this.getDataDirectory(), fileName); + } + +- public void logWarning(String msg) +- { ++ public void logWarning(String msg) { + LOGGER.warn(msg); + } + +@@ -835,53 +985,51 @@ + return ret; + } + +- public String getMinecraftVersion() +- { ++ public WorldServer getWorldServer(int i) { ++ WorldServer world = getWorld(i); ++ return world != null ? world : worlds[0]; ++ } ++ ++ public String getMinecraftVersion() { + return "1.12.2"; + } + +- public int getCurrentPlayerCount() +- { ++ public int getCurrentPlayerCount() { + return this.playerList.getCurrentPlayerCount(); + } + +- public int getMaxPlayers() +- { ++ public int getMaxPlayers() { + return this.playerList.getMaxPlayers(); + } + +- public String[] getOnlinePlayerNames() +- { ++ public String[] getOnlinePlayerNames() { + return this.playerList.getOnlinePlayerNames(); + } + +- public GameProfile[] getOnlinePlayerProfiles() +- { ++ public GameProfile[] getOnlinePlayerProfiles() { + return this.playerList.getOnlinePlayerProfiles(); + } + +- public String getServerModName() +- { ++ public String getServerModName() { ++ // TODO: Should we change this for CraftBukkit's server name? + return net.minecraftforge.fml.common.FMLCommonHandler.instance().getModName(); + } + +- public CrashReport addServerInfoToCrashReport(CrashReport report) +- { +- report.getCategory().addDetail("Profiler Position", new ICrashReportDetail() +- { +- public String call() throws Exception +- { +- return MinecraftServer.this.profiler.profilingEnabled ? MinecraftServer.this.profiler.getNameOfLastSection() : "N/A (disabled)"; ++ public CrashReport addServerInfoToCrashReport(CrashReport report) { ++ report.getCategory().addDetail("Profiler Position", new ICrashReportDetail() { ++ public String call() throws Exception { ++ return MinecraftServer.this.profiler.profilingEnabled ++ ? MinecraftServer.this.profiler.getNameOfLastSection() ++ : "N/A (disabled)"; + } + }); + +- if (this.playerList != null) +- { +- report.getCategory().addDetail("Player Count", new ICrashReportDetail() +- { +- public String call() +- { +- return MinecraftServer.this.playerList.getCurrentPlayerCount() + " / " + MinecraftServer.this.playerList.getMaxPlayers() + "; " + MinecraftServer.this.playerList.getPlayers(); ++ if (this.playerList != null) { ++ report.getCategory().addDetail("Player Count", new ICrashReportDetail() { ++ public String call() { ++ return MinecraftServer.this.playerList.getCurrentPlayerCount() + " / " ++ + MinecraftServer.this.playerList.getMaxPlayers() + "; " ++ + MinecraftServer.this.playerList.getPlayers(); + } + }); + } +@@ -889,145 +1037,115 @@ + return report; + } + +- public List getTabCompletions(ICommandSender sender, String input, @Nullable BlockPos pos, boolean hasTargetBlock) +- { +- List list = Lists.newArrayList(); ++ public List getTabCompletions(ICommandSender sender, String input, @Nullable BlockPos pos, ++ boolean hasTargetBlock) { ++ Set completionsSet = Sets.newHashSet(server.tabComplete(sender, input, pos, hasTargetBlock)); + boolean flag = input.startsWith("/"); + +- if (flag) +- { ++ if (flag) { + input = input.substring(1); + } + +- if (!flag && !hasTargetBlock) +- { ++ if (!flag && !hasTargetBlock) { + String[] astring = input.split(" ", -1); + String s2 = astring[astring.length - 1]; + +- for (String s1 : this.playerList.getOnlinePlayerNames()) +- { +- if (CommandBase.doesStringStartWith(s2, s1)) +- { +- list.add(s1); ++ for (String s1 : this.playerList.getOnlinePlayerNames()) { ++ if (CommandBase.doesStringStartWith(s2, s1)) { ++ completionsSet.add(s1); + } + } +- +- return list; +- } +- else +- { ++ } else { + boolean flag1 = !input.contains(" "); + List list1 = this.commandManager.getTabCompletions(sender, input, pos); + +- if (!list1.isEmpty()) +- { +- for (String s : list1) +- { +- if (flag1 && !hasTargetBlock) +- { +- list.add("/" + s); ++ if (!list1.isEmpty()) { ++ for (String s : list1) { ++ if (flag1 && !hasTargetBlock) { ++ completionsSet.add("/" + s); ++ } else { ++ completionsSet.add(s); + } +- else +- { +- list.add(s); +- } + } + } +- +- return list; + } ++ List finalCompletionsList = new ArrayList<>(completionsSet); ++ Collections.sort(finalCompletionsList); ++ return finalCompletionsList; + } + +- public boolean isAnvilFileSet() +- { +- return this.anvilFile != null; ++ public boolean isAnvilFileSet() { ++ // return this.anvilFile != null; ++ return true; // CraftBukkit + } + +- public String getName() +- { ++ public String getName() { + return "Server"; + } + +- public void sendMessage(ITextComponent component) +- { ++ public void sendMessage(ITextComponent component) { + LOGGER.info(component.getUnformattedText()); + } + +- public boolean canUseCommand(int permLevel, String commandName) +- { ++ public boolean canUseCommand(int permLevel, String commandName) { + return true; + } + +- public ICommandManager getCommandManager() ++ public boolean canUseCommand(int permLevel, String commandName, String perm) + { ++ return true; ++ } ++ ++ public ICommandManager getCommandManager() { + return this.commandManager; + } + +- public KeyPair getKeyPair() +- { ++ public KeyPair getKeyPair() { + return this.serverKeyPair; + } + +- public String getServerOwner() +- { ++ public String getServerOwner() { + return this.serverOwner; + } + +- public void setServerOwner(String owner) +- { ++ public void setServerOwner(String owner) { + this.serverOwner = owner; + } + +- public boolean isSinglePlayer() +- { ++ public boolean isSinglePlayer() { + return this.serverOwner != null; + } + +- public String getFolderName() +- { ++ public String getFolderName() { + return this.folderName; + } + +- public void setFolderName(String name) +- { ++ public void setFolderName(String name) { + this.folderName = name; + } + +- @SideOnly(Side.CLIENT) +- public void setWorldName(String worldNameIn) +- { ++ public void setWorldName(String worldNameIn) { + this.worldName = worldNameIn; + } + +- @SideOnly(Side.CLIENT) +- public String getWorldName() +- { ++ public String getWorldName() { + return this.worldName; + } + +- public void setKeyPair(KeyPair keyPair) +- { ++ public void setKeyPair(KeyPair keyPair) { + this.serverKeyPair = keyPair; + } + +- public void setDifficultyForAllWorlds(EnumDifficulty difficulty) +- { +- for (WorldServer worldserver1 : this.worlds) +- { +- if (worldserver1 != null) +- { +- if (worldserver1.getWorldInfo().isHardcoreModeEnabled()) +- { ++ public void setDifficultyForAllWorlds(EnumDifficulty difficulty) { ++ for (WorldServer worldserver1 : this.worldServerList) { ++ if (worldserver1 != null) { ++ if (worldserver1.getWorldInfo().isHardcoreModeEnabled()) { + worldserver1.getWorldInfo().setDifficulty(EnumDifficulty.HARD); + worldserver1.setAllowedSpawnTypes(true, true); +- } +- else if (this.isSinglePlayer()) +- { ++ } else if (this.isSinglePlayer()) { + worldserver1.getWorldInfo().setDifficulty(difficulty); + worldserver1.setAllowedSpawnTypes(worldserver1.getDifficulty() != EnumDifficulty.PEACEFUL, true); +- } +- else +- { ++ } else { + worldserver1.getWorldInfo().setDifficulty(difficulty); + worldserver1.setAllowedSpawnTypes(this.allowSpawnMonsters(), this.canSpawnAnimals); + } +@@ -1035,80 +1153,75 @@ + } + } + +- public boolean allowSpawnMonsters() +- { ++ public boolean allowSpawnMonsters() { + return true; + } + +- public boolean isDemo() +- { ++ public boolean isDemo() { + return this.isDemo; + } + +- public void setDemo(boolean demo) +- { ++ public void setDemo(boolean demo) { + this.isDemo = demo; + } + +- public void canCreateBonusChest(boolean enable) +- { ++ public void canCreateBonusChest(boolean enable) { + this.enableBonusChest = enable; + } + +- public ISaveFormat getActiveAnvilConverter() +- { ++ public ISaveFormat getActiveAnvilConverter() { + return this.anvilConverterForAnvilFile; + } + +- public String getResourcePackUrl() +- { ++ public String getResourcePackUrl() { + return this.resourcePackUrl; + } + +- public String getResourcePackHash() +- { ++ public String getResourcePackHash() { + return this.resourcePackHash; + } + +- public void setResourcePack(String url, String hash) +- { ++ public void setResourcePack(String url, String hash) { + this.resourcePackUrl = url; + this.resourcePackHash = hash; + } + +- public void addServerStatsToSnooper(Snooper playerSnooper) +- { ++ public void addServerStatsToSnooper(Snooper playerSnooper) { + playerSnooper.addClientStat("whitelist_enabled", Boolean.valueOf(false)); + playerSnooper.addClientStat("whitelist_count", Integer.valueOf(0)); + +- if (this.playerList != null) +- { ++ if (this.playerList != null) { + playerSnooper.addClientStat("players_current", Integer.valueOf(this.getCurrentPlayerCount())); + playerSnooper.addClientStat("players_max", Integer.valueOf(this.getMaxPlayers())); +- playerSnooper.addClientStat("players_seen", Integer.valueOf(this.playerList.getAvailablePlayerDat().length)); ++ playerSnooper.addClientStat("players_seen", ++ Integer.valueOf(this.playerList.getAvailablePlayerDat().length)); + } + + playerSnooper.addClientStat("uses_auth", Boolean.valueOf(this.onlineMode)); + playerSnooper.addClientStat("gui_state", this.getGuiEnabled() ? "enabled" : "disabled"); +- playerSnooper.addClientStat("run_time", Long.valueOf((getCurrentTimeMillis() - playerSnooper.getMinecraftStartTimeMillis()) / 60L * 1000L)); +- playerSnooper.addClientStat("avg_tick_ms", Integer.valueOf((int)(MathHelper.average(this.tickTimeArray) * 1.0E-6D))); ++ playerSnooper.addClientStat("run_time", ++ Long.valueOf((getCurrentTimeMillis() - playerSnooper.getMinecraftStartTimeMillis()) / 60L * 1000L)); ++ playerSnooper.addClientStat("avg_tick_ms", ++ Integer.valueOf((int) (MathHelper.average(this.tickTimeArray) * 1.0E-6D))); + int l = 0; + +- if (this.worlds != null) +- { +- for (WorldServer worldserver1 : this.worlds) +- { +- if (worldserver1 != null) +- { ++ if (this.worldServerList != null) { ++ for (WorldServer worldserver1 : this.worldServerList) { ++ if (worldserver1 != null) { + WorldInfo worldinfo = worldserver1.getWorldInfo(); +- playerSnooper.addClientStat("world[" + l + "][dimension]", Integer.valueOf(worldserver1.provider.getDimensionType().getId())); ++ playerSnooper.addClientStat("world[" + l + "][dimension]", ++ Integer.valueOf(worldserver1.provider.getDimensionType().getId())); + playerSnooper.addClientStat("world[" + l + "][mode]", worldinfo.getGameType()); + playerSnooper.addClientStat("world[" + l + "][difficulty]", worldserver1.getDifficulty()); +- playerSnooper.addClientStat("world[" + l + "][hardcore]", Boolean.valueOf(worldinfo.isHardcoreModeEnabled())); +- playerSnooper.addClientStat("world[" + l + "][generator_name]", worldinfo.getTerrainType().getName()); +- playerSnooper.addClientStat("world[" + l + "][generator_version]", Integer.valueOf(worldinfo.getTerrainType().getVersion())); ++ playerSnooper.addClientStat("world[" + l + "][hardcore]", ++ Boolean.valueOf(worldinfo.isHardcoreModeEnabled())); ++ playerSnooper.addClientStat("world[" + l + "][generator_name]", ++ worldinfo.getTerrainType().getName()); ++ playerSnooper.addClientStat("world[" + l + "][generator_version]", ++ Integer.valueOf(worldinfo.getTerrainType().getVersion())); + playerSnooper.addClientStat("world[" + l + "][height]", Integer.valueOf(this.buildLimit)); +- playerSnooper.addClientStat("world[" + l + "][chunks_loaded]", Integer.valueOf(worldserver1.getChunkProvider().getLoadedChunkCount())); ++ playerSnooper.addClientStat("world[" + l + "][chunks_loaded]", ++ Integer.valueOf(worldserver1.getChunkProvider().getLoadedChunkCount())); + ++l; + } + } +@@ -1117,228 +1230,186 @@ + playerSnooper.addClientStat("worlds", Integer.valueOf(l)); + } + +- public void addServerTypeToSnooper(Snooper playerSnooper) +- { ++ public void addServerTypeToSnooper(Snooper playerSnooper) { + playerSnooper.addStatToSnooper("singleplayer", Boolean.valueOf(this.isSinglePlayer())); + playerSnooper.addStatToSnooper("server_brand", this.getServerModName()); + playerSnooper.addStatToSnooper("gui_supported", GraphicsEnvironment.isHeadless() ? "headless" : "supported"); + playerSnooper.addStatToSnooper("dedicated", Boolean.valueOf(this.isDedicatedServer())); + } + +- public boolean isSnooperEnabled() +- { ++ public boolean isSnooperEnabled() { + return true; + } + + public abstract boolean isDedicatedServer(); + +- public boolean isServerInOnlineMode() +- { +- return this.onlineMode; ++ public boolean isServerInOnlineMode() { ++ // return this.onlineMode; ++ return server.getOnlineMode(); // CraftBukkit + } + +- public void setOnlineMode(boolean online) +- { ++ public void setOnlineMode(boolean online) { + this.onlineMode = online; + } + +- public boolean getPreventProxyConnections() +- { ++ public boolean getPreventProxyConnections() { + return this.preventProxyConnections; + } + +- public boolean getCanSpawnAnimals() +- { ++ public boolean getCanSpawnAnimals() { + return this.canSpawnAnimals; + } + +- public void setCanSpawnAnimals(boolean spawnAnimals) +- { ++ public void setCanSpawnAnimals(boolean spawnAnimals) { + this.canSpawnAnimals = spawnAnimals; + } + +- public boolean getCanSpawnNPCs() +- { ++ public boolean getCanSpawnNPCs() { + return this.canSpawnNPCs; + } + + public abstract boolean shouldUseNativeTransport(); + +- public void setCanSpawnNPCs(boolean spawnNpcs) +- { ++ public void setCanSpawnNPCs(boolean spawnNpcs) { + this.canSpawnNPCs = spawnNpcs; + } + +- public boolean isPVPEnabled() +- { ++ public boolean isPVPEnabled() { + return this.pvpEnabled; + } + +- public void setAllowPvp(boolean allowPvp) +- { ++ public void setAllowPvp(boolean allowPvp) { + this.pvpEnabled = allowPvp; + } + +- public boolean isFlightAllowed() +- { ++ public boolean isFlightAllowed() { + return this.allowFlight; + } + +- public void setAllowFlight(boolean allow) +- { ++ public void setAllowFlight(boolean allow) { + this.allowFlight = allow; + } + + public abstract boolean isCommandBlockEnabled(); + +- public String getMOTD() +- { ++ public String getMOTD() { + return this.motd; + } + +- public void setMOTD(String motdIn) +- { ++ public void setMOTD(String motdIn) { + this.motd = motdIn; + } + +- public int getBuildLimit() +- { ++ public int getBuildLimit() { + return this.buildLimit; + } + +- public void setBuildLimit(int maxBuildHeight) +- { ++ public void setBuildLimit(int maxBuildHeight) { + this.buildLimit = maxBuildHeight; + } + +- public boolean isServerStopped() +- { ++ public boolean isServerStopped() { + return this.serverStopped; + } + +- public PlayerList getPlayerList() +- { ++ public PlayerList getPlayerList() { + return this.playerList; + } + +- public void setPlayerList(PlayerList list) +- { ++ public void setPlayerList(PlayerList list) { + this.playerList = list; + } + +- public void setGameType(GameType gameMode) +- { +- for (WorldServer worldserver1 : this.worlds) +- { ++ public void setGameType(GameType gameMode) { ++ for (WorldServer worldserver1 : this.worldServerList) { + worldserver1.getWorldInfo().setGameType(gameMode); + } + } + +- public NetworkSystem getNetworkSystem() +- { ++ public NetworkSystem getNetworkSystem() { + return this.networkSystem; + } + + @SideOnly(Side.CLIENT) +- public boolean serverIsInRunLoop() +- { ++ public boolean serverIsInRunLoop() { + return this.serverIsRunning; + } + +- public boolean getGuiEnabled() +- { ++ public boolean getGuiEnabled() { + return false; + } + + public abstract String shareToLAN(GameType type, boolean allowCheats); + +- public int getTickCounter() +- { ++ public int getTickCounter() { + return this.tickCounter; + } + +- public void enableProfiling() +- { ++ public void enableProfiling() { + this.startProfiling = true; + } + + @SideOnly(Side.CLIENT) +- public Snooper getPlayerUsageSnooper() +- { ++ public Snooper getPlayerUsageSnooper() { + return this.usageSnooper; + } + +- public World getEntityWorld() +- { +- return this.worlds[0]; ++ public World getEntityWorld() { ++ // return this.worlds[0]; ++ return this.worlds[0]; // CraftBukkit + } + +- public boolean isBlockProtected(World worldIn, BlockPos pos, EntityPlayer playerIn) +- { ++ public boolean isBlockProtected(World worldIn, BlockPos pos, EntityPlayer playerIn) { + return false; + } + +- public boolean getForceGamemode() +- { ++ public boolean getForceGamemode() { + return this.isGamemodeForced; + } + +- public Proxy getServerProxy() +- { ++ public Proxy getServerProxy() { + return this.serverProxy; + } + +- public static long getCurrentTimeMillis() +- { ++ public static long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + +- public int getMaxPlayerIdleMinutes() +- { ++ public int getMaxPlayerIdleMinutes() { + return this.maxPlayerIdleMinutes; + } + +- public void setPlayerIdleTimeout(int idleTimeout) +- { ++ public void setPlayerIdleTimeout(int idleTimeout) { + this.maxPlayerIdleMinutes = idleTimeout; + } + +- public MinecraftSessionService getMinecraftSessionService() +- { ++ public MinecraftSessionService getMinecraftSessionService() { + return this.sessionService; + } + +- public GameProfileRepository getGameProfileRepository() +- { ++ public GameProfileRepository getGameProfileRepository() { + return this.profileRepo; + } + +- public PlayerProfileCache getPlayerProfileCache() +- { ++ public PlayerProfileCache getPlayerProfileCache() { + return this.profileCache; + } + +- public ServerStatusResponse getServerStatusResponse() +- { ++ public ServerStatusResponse getServerStatusResponse() { + return this.statusResponse; + } + +- public void refreshStatusNextTick() +- { ++ public void refreshStatusNextTick() { + this.nanoTimeSinceStatusRefresh = 0L; + } + + @Nullable +- public Entity getEntityFromUuid(UUID uuid) +- { +- for (WorldServer worldserver1 : this.worlds) +- { +- if (worldserver1 != null) +- { ++ public Entity getEntityFromUuid(UUID uuid) { ++ for (WorldServer worldserver1 : this.worldServerList) { ++ if (worldserver1 != null) { + Entity entity = worldserver1.getEntityFromUuid(uuid); + +- if (entity != null) +- { ++ if (entity != null) { + return entity; + } + } +@@ -1347,316 +1418,212 @@ + return null; + } + +- public boolean sendCommandFeedback() +- { +- return this.worlds[0].getGameRules().getBoolean("sendCommandFeedback"); ++ public boolean sendCommandFeedback() { ++ // return this.worlds[0].getGameRules().getBoolean("sendCommandFeedback"); ++ return worlds[0].getGameRules().getBoolean("sendCommandFeedback"); + } + +- public MinecraftServer getServer() +- { ++ public MinecraftServer getServer() { + return this; + } + +- public int getMaxWorldSize() +- { ++ public static MinecraftServer getServerInstance() { ++ return instance; ++ } ++ ++ @Nullable ++ @Deprecated ++ public static MinecraftServer getServerCB() { ++ return (Bukkit.getServer() instanceof CraftServer) ? ((CraftServer) Bukkit.getServer()).getServer() : null; ++ } ++ ++ public int getMaxWorldSize() { + return 29999984; + } + +- public ListenableFuture callFromMainThread(Callable callable) +- { ++ public ListenableFuture callFromMainThread(Callable callable) { + Validate.notNull(callable); + +- if (!this.isCallingFromMinecraftThread() && !this.isServerStopped()) ++ if (!this.isCallingFromMinecraftThread()/* && !this.isServerStopped()*/) // CraftBukkit + { + ListenableFutureTask listenablefuturetask = ListenableFutureTask.create(callable); + +- synchronized (this.futureTaskQueue) +- { ++ // Spigot start + this.futureTaskQueue.add(listenablefuturetask); + return listenablefuturetask; ++ // Spigot end + } +- } + else + { +- try +- { ++ try { + return Futures.immediateFuture(callable.call()); +- } +- catch (Exception exception) +- { ++ } catch (Exception exception) { + return Futures.immediateFailedCheckedFuture(exception); + } + } + } + +- public ListenableFuture addScheduledTask(Runnable runnableToSchedule) +- { ++ public ListenableFuture addScheduledTask(Runnable runnableToSchedule) { + Validate.notNull(runnableToSchedule); + return this.callFromMainThread(Executors.callable(runnableToSchedule)); + } + +- public boolean isCallingFromMinecraftThread() +- { ++ public boolean isCallingFromMinecraftThread() { + return Thread.currentThread() == this.serverThread; + } + +- public int getNetworkCompressionThreshold() +- { ++ public int getNetworkCompressionThreshold() { + return 256; + } + +- public int getSpawnRadius(@Nullable WorldServer worldIn) +- { ++ public int getSpawnRadius(@Nullable WorldServer worldIn) { + return worldIn != null ? worldIn.getGameRules().getInt("spawnRadius") : 10; + } + +- public AdvancementManager getAdvancementManager() +- { ++ public AdvancementManager getAdvancementManager() { ++ // return this.worlds[0].getAdvancementManager(); + return this.worlds[0].getAdvancementManager(); + } + +- public FunctionManager getFunctionManager() +- { ++ public FunctionManager getFunctionManager() { ++ // return this.worlds[0].getFunctionManager(); + return this.worlds[0].getFunctionManager(); + } + +- public void reload() +- { +- if (this.isCallingFromMinecraftThread()) +- { ++ public void reload() { ++ if (this.isCallingFromMinecraftThread()) { + this.getPlayerList().saveAllPlayerData(); ++ // this.worlds[0].getLootTableManager().reloadLootTables(); + this.worlds[0].getLootTableManager().reloadLootTables(); + this.getAdvancementManager().reload(); + this.getFunctionManager().reload(); + this.getPlayerList().reloadResources(); +- } +- else +- { ++ } else { + this.addScheduledTask(this::reload); + } + } + + @SideOnly(Side.SERVER) +- public String getServerHostname() +- { ++ public String getServerHostname() { + return this.hostname; + } + + @SideOnly(Side.SERVER) +- public void setHostname(String host) +- { ++ public void setHostname(String host) { + this.hostname = host; + } + + @SideOnly(Side.SERVER) +- public void registerTickable(ITickable tickable) +- { ++ public void registerTickable(ITickable tickable) { + this.tickables.add(tickable); + } + + @SideOnly(Side.SERVER) +- public static void main(String[] p_main_0_) +- { ++ public static void main(String[] args) { ++ OptionSet options = org.bukkit.craftbukkit.Main.main(args); ++ if (options == null) ++ return; + //Forge: Copied from DedicatedServer.init as to run as early as possible, Old code left in place intentionally. + //Done in good faith with permission: https://github.com/MinecraftForge/MinecraftForge/issues/3659#issuecomment-390467028 + ServerEula eula = new ServerEula(new File("eula.txt")); +- if (!eula.hasAcceptedEULA()) +- { ++ if (!eula.hasAcceptedEULA()) { + LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); + eula.createEULAFile(); + return; + } +- + Bootstrap.register(); +- +- try +- { +- boolean flag = true; +- String s = null; +- String s1 = "."; +- String s2 = null; +- boolean flag1 = false; +- boolean flag2 = false; +- int l = -1; +- +- for (int i1 = 0; i1 < p_main_0_.length; ++i1) +- { +- String s3 = p_main_0_[i1]; +- String s4 = i1 == p_main_0_.length - 1 ? null : p_main_0_[i1 + 1]; +- boolean flag3 = false; +- +- if (!"nogui".equals(s3) && !"--nogui".equals(s3)) +- { +- if ("--port".equals(s3) && s4 != null) +- { +- flag3 = true; +- + try + { +- l = Integer.parseInt(s4); +- } +- catch (NumberFormatException var13) +- { +- ; +- } +- } +- else if ("--singleplayer".equals(s3) && s4 != null) +- { +- flag3 = true; +- s = s4; +- } +- else if ("--universe".equals(s3) && s4 != null) +- { +- flag3 = true; +- s1 = s4; +- } +- else if ("--world".equals(s3) && s4 != null) +- { +- flag3 = true; +- s2 = s4; +- } +- else if ("--demo".equals(s3)) +- { +- flag1 = true; +- } +- else if ("--bonusChest".equals(s3)) +- { +- flag2 = true; +- } +- } +- else +- { +- flag = false; +- } ++ String s1 = "."; ++ YggdrasilAuthenticationService yggdrasilauthenticationservice = new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString()); // Paper ++ MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice ++ .createMinecraftSessionService(); ++ GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); ++ PlayerProfileCache playerprofilecache = new PlayerProfileCache(gameprofilerepository, ++ new File(s1, USER_CACHE_FILE.getName())); ++ final DedicatedServer dedicatedserver = new DedicatedServer(options, DataFixesManager.createFixer(), ++ yggdrasilauthenticationservice, minecraftsessionservice, gameprofilerepository, playerprofilecache); + +- if (flag3) +- { +- ++i1; ++ if (options.has("port")) { ++ int port = (Integer) options.valueOf("port"); ++ if (port > 0) { ++ dedicatedserver.setServerPort(port); + } + } + +- YggdrasilAuthenticationService yggdrasilauthenticationservice = new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString()); +- MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService(); +- GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); +- PlayerProfileCache playerprofilecache = new PlayerProfileCache(gameprofilerepository, new File(s1, USER_CACHE_FILE.getName())); +- final DedicatedServer dedicatedserver = new DedicatedServer(new File(s1), DataFixesManager.createFixer(), yggdrasilauthenticationservice, minecraftsessionservice, gameprofilerepository, playerprofilecache); +- +- if (s != null) +- { +- dedicatedserver.setServerOwner(s); ++ if (options.has("universe")) { ++ dedicatedserver.anvilFile = (File) options.valueOf("universe"); + } + +- if (s2 != null) +- { +- dedicatedserver.setFolderName(s2); ++ if (options.has("world")) { ++ dedicatedserver.setWorldName((String) options.valueOf("world")); + } + +- if (l >= 0) +- { +- dedicatedserver.setServerPort(l); +- } +- +- if (flag1) +- { +- dedicatedserver.setDemo(true); +- } +- +- if (flag2) +- { +- dedicatedserver.canCreateBonusChest(true); +- } +- +- if (flag && !GraphicsEnvironment.isHeadless()) +- { +- dedicatedserver.setGuiEnabled(); +- } +- +- dedicatedserver.startServerThread(); +- Runtime.getRuntime().addShutdownHook(new Thread("Server Shutdown Thread") +- { +- public void run() +- { +- dedicatedserver.stopServer(); +- } +- }); ++ dedicatedserver.primaryThread.start(); ++ } catch (Exception exception) { ++ LOGGER.fatal("Failed to start the minecraft server", (Throwable) exception); + } +- catch (Exception exception) +- { +- LOGGER.fatal("Failed to start the minecraft server", (Throwable)exception); +- } + } + + @SideOnly(Side.SERVER) +- public void logInfo(String msg) +- { ++ public void logInfo(String msg) { + LOGGER.info(msg); + } + + @SideOnly(Side.SERVER) +- public boolean isDebuggingEnabled() +- { +- return false; ++ public boolean isDebuggingEnabled() { ++ // return false; ++ return this.getPropertyManager().getBooleanProperty("debug", false); // CraftBukkit - don't hardcode + } + + @SideOnly(Side.SERVER) +- public void logSevere(String msg) +- { ++ public void logSevere(String msg) { + LOGGER.error(msg); + } + + @SideOnly(Side.SERVER) +- public void logDebug(String msg) +- { +- if (this.isDebuggingEnabled()) +- { ++ public void logDebug(String msg) { ++ if (this.isDebuggingEnabled()) { + LOGGER.info(msg); + } + } + + @SideOnly(Side.SERVER) +- public int getServerPort() +- { ++ public int getServerPort() { + return this.serverPort; + } + + @SideOnly(Side.SERVER) +- public void setServerPort(int port) +- { ++ public void setServerPort(int port) { + this.serverPort = port; + } + + @SideOnly(Side.SERVER) +- public void setPreventProxyConnections(boolean p_190517_1_) +- { ++ public void setPreventProxyConnections(boolean p_190517_1_) { + this.preventProxyConnections = p_190517_1_; + } + + @SideOnly(Side.SERVER) +- public int getSpawnProtectionSize() +- { ++ public int getSpawnProtectionSize() { + return 16; + } + + @SideOnly(Side.SERVER) +- public void setForceGamemode(boolean force) +- { ++ public void setForceGamemode(boolean force) { + this.isGamemodeForced = force; + } + + @SideOnly(Side.SERVER) +- public long getCurrentTime() +- { ++ public long getCurrentTime() { + return this.currentTime; + } + + @SideOnly(Side.SERVER) +- public Thread getServerThread() +- { ++ public Thread getServerThread() { + return this.serverThread; + } + +- public DataFixer getDataFixer() +- { ++ public DataFixer getDataFixer() { + return this.dataFixer; + } + } diff --git a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch new file mode 100644 index 00000000..819bbe72 --- /dev/null +++ b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -0,0 +1,332 @@ +--- ../src-base/minecraft/net/minecraft/server/dedicated/DedicatedServer.java ++++ ../src-work/minecraft/net/minecraft/server/dedicated/DedicatedServer.java +@@ -4,10 +4,7 @@ + import com.mojang.authlib.GameProfileRepository; + import com.mojang.authlib.minecraft.MinecraftSessionService; + import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +-import java.io.BufferedReader; +-import java.io.File; +-import java.io.IOException; +-import java.io.InputStreamReader; ++import java.io.*; + import java.net.InetAddress; + import java.net.Proxy; + import java.nio.charset.StandardCharsets; +@@ -43,30 +40,43 @@ + import net.minecraft.world.World; + import net.minecraft.world.WorldSettings; + import net.minecraft.world.WorldType; ++import net.minecraft.world.chunk.storage.AnvilSaveConverter; ++import net.minecraftforge.fml.relauncher.ServerLaunchWrapper; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.apache.logging.log4j.Level; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.LoggerOutputStream; ++import org.bukkit.craftbukkit.Main; ++import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.event.server.RemoteServerCommandEvent; ++import org.bukkit.event.server.ServerCommandEvent; + ++import org.spigotmc.SpigotConfig; ++ + @SideOnly(Side.SERVER) + public class DedicatedServer extends MinecraftServer implements IServer + { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Pattern RESOURCE_PACK_SHA1_PATTERN = Pattern.compile("^[a-fA-F0-9]{40}$"); +- public final List pendingCommandList = Collections.synchronizedList(Lists.newArrayList()); ++ public final java.util.Queue pendingCommandList = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - use a proper queue + private RConThreadQuery rconQueryThread; +- private final RConConsoleSource rconConsoleSource = new RConConsoleSource(this); ++ public final RConConsoleSource rconConsoleSource = new RConConsoleSource(this); + private RConThreadMain rconThread; +- private PropertyManager settings; ++ public PropertyManager settings; + private ServerEula eula; + private boolean canSpawnStructures; + private GameType gameType; + private boolean guiIsEnabled; + public static boolean allowPlayerLogins = false; + +- public DedicatedServer(File anvilFileIn, DataFixer dataFixerIn, YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) ++ // CraftBukkit start - Signature changed ++ public DedicatedServer(joptsimple.OptionSet options, DataFixer dataFixerIn, YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) + { +- super(anvilFileIn, Proxy.NO_PROXY, dataFixerIn, authServiceIn, sessionServiceIn, profileRepoIn, profileCacheIn); ++ super(options, Proxy.NO_PROXY, dataFixerIn, authServiceIn, sessionServiceIn, profileRepoIn, profileCacheIn); + Thread thread = new Thread("Server Infinisleeper") + { + { +@@ -96,15 +106,23 @@ + { + public void run() + { +- if (net.minecraftforge.server.console.TerminalHandler.handleCommands(DedicatedServer.this)) return; +- BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); ++ if (!Main.useConsole) { ++ return; ++ } ++ jline.console.ConsoleReader bufferedreader = reader; + String s4; + +- try +- { +- while (!DedicatedServer.this.isServerStopped() && DedicatedServer.this.isServerRunning() && (s4 = bufferedreader.readLine()) != null) +- { +- DedicatedServer.this.addPendingCommand(s4, DedicatedServer.this); ++ ++ try { ++ while (!isServerStopped() && isServerRunning()) { ++ if (Main.useJline) { ++ s4 = bufferedreader.readLine(">", null); ++ } else { ++ s4 = bufferedreader.readLine(); ++ } ++ if (s4 != null && s4.trim().length() > 0) { // Trim to filter lines which are just spaces ++ addPendingCommand(s4, DedicatedServer.this); ++ } + } + } + catch (IOException ioexception1) +@@ -113,6 +131,27 @@ + } + } + }; ++ ++ // CraftBukkit start - TODO: handle command-line logging arguments ++ java.util.logging.Logger global = java.util.logging.Logger.getLogger(""); ++ global.setUseParentHandlers(false); ++ for (java.util.logging.Handler handler : global.getHandlers()) { ++ global.removeHandler(handler); ++ } ++ global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); ++ ++ final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); ++ for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { ++ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { ++ logger.removeAppender(appender); ++ } ++ } ++ ++ new Thread((Runnable)new TerminalConsoleWriterThread((OutputStream)System.out, this.reader)).start(); ++ System.setOut(new PrintStream(new LoggerOutputStream(logger, Level.INFO), true)); ++ System.setErr(new PrintStream(new LoggerOutputStream(logger, Level.WARN), true)); ++ // CraftBukkit end ++ + thread.setDaemon(true); + thread.start(); + LOGGER.info("Starting minecraft server version 1.12.2"); +@@ -125,7 +164,7 @@ + net.minecraftforge.fml.common.FMLCommonHandler.instance().onServerStart(this); + + LOGGER.info("Loading properties"); +- this.settings = new PropertyManager(new File("server.properties")); ++ this.settings = new PropertyManager(this.options); // CraftBukkit - CLI argument support + this.eula = new ServerEula(new File("eula.txt")); + + if (!this.eula.hasAcceptedEULA()) +@@ -144,7 +183,7 @@ + { + this.setOnlineMode(this.settings.getBooleanProperty("online-mode", true)); + this.setPreventProxyConnections(this.settings.getBooleanProperty("prevent-proxy-connections", false)); +- this.setHostname(this.settings.getStringProperty("server-ip", "")); ++ this.setHostname(this.settings.getStringProperty("server-ip", "0.0.0.0")); + } + + this.setCanSpawnAnimals(this.settings.getBooleanProperty("spawn-animals", true)); +@@ -180,11 +219,17 @@ + { + this.setServerPort(this.settings.getIntProperty("server-port", 25565)); + } ++ // Spigot start ++ this.setPlayerList(new DedicatedPlayerList(this)); ++ SpigotConfig.init((File) options.valueOf("spigot-settings")); ++ SpigotConfig.registerCommands(); ++ // Spigot end + + LOGGER.info("Generating keypair"); + this.setKeyPair(CryptManager.generateKeyPair()); + LOGGER.info("Starting Minecraft server on {}:{}", this.getServerHostname().isEmpty() ? "*" : this.getServerHostname(), Integer.valueOf(this.getServerPort())); + ++ if(!SpigotConfig.lateBind) { + try + { + this.getNetworkSystem().addLanEndpoint(inetaddress, this.getServerPort()); +@@ -196,13 +241,6 @@ + LOGGER.warn("Perhaps a server is already running on that port?"); + return false; + } +- +- if (!this.isServerInOnlineMode()) +- { +- LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); +- LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); +- LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); +- LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + + if (this.convertFiles()) +@@ -217,8 +255,7 @@ + else + { + net.minecraftforge.fml.common.FMLCommonHandler.instance().onServerStarted(); +- this.setPlayerList(new DedicatedPlayerList(this)); +- long j = System.nanoTime(); ++ this.anvilConverterForAnvilFile = new AnvilSaveConverter(server.getWorldContainer(), this.dataFixer); // CraftBukkit - moved from MinecraftServer constructor + + if (this.getFolderName() == null) + { +@@ -268,11 +305,11 @@ + if (!net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerAboutToStart(this)) return false; + LOGGER.info("Preparing level \"{}\"", (Object)this.getFolderName()); + this.loadAllWorlds(this.getFolderName(), this.getFolderName(), k, worldtype, s2); +- long i1 = System.nanoTime() - j; ++ long i1 = System.nanoTime() - ServerLaunchWrapper.beginTime; + String s3 = String.format("%.3fs", (double)i1 / 1.0E9D); + LOGGER.info("Done ({})! For help, type \"help\" or \"?\"", (Object)s3); +- this.currentTime = getCurrentTimeMillis(); + ++ LOGGER.info(String.format("Starting Minecraft server on {0}:{1}", this.getServerHostname().isEmpty() ? "0.0.0.0" : this.getServerHostname(), String.valueOf(this.getServerPort()))); + if (this.settings.hasProperty("announce-player-achievements")) + { + this.worlds[0].getGameRules().setOrCreateGameRule("announceAdvancements", this.settings.getBooleanProperty("announce-player-achievements", true) ? "true" : "false"); +@@ -287,21 +324,32 @@ + this.rconQueryThread.startThread(); + } + +- if (this.settings.getBooleanProperty("enable-rcon", false)) +- { ++ if (this.settings.getBooleanProperty("enable-rcon", false)) { + LOGGER.info("Starting remote control listener"); + this.rconThread = new RConThreadMain(this); + this.rconThread.startThread(); ++ this.remoteConsole = new org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender(this.rconConsoleSource); + } + +- if (this.getMaxTickTime() > 0L) +- { +- Thread thread1 = new Thread(new ServerHangWatchdog(this)); +- thread1.setName("Server Watchdog"); +- thread1.setDaemon(true); +- thread1.start(); ++ if (this.server.getBukkitSpawnRadius() > -1) { ++ DedicatedServer.LOGGER.info("'settings.spawn-radius' in bukkit.yml has been moved to 'spawn-protection' in server.properties. I will move your config for you."); ++ this.settings.serverProperties.remove("spawn-protection"); ++ this.settings.getIntProperty("spawn-protection", this.server.getBukkitSpawnRadius()); ++ this.server.removeBukkitSpawnRadius(); ++ this.settings.saveProperties(); + } + ++ if (SpigotConfig.lateBind) { ++ try { ++ this.getNetworkSystem().addLanEndpoint(inetaddress, this.getServerPort()); ++ } catch (IOException ioexception) { ++ LOGGER.warn("**** FAILED TO BIND TO PORT!"); ++ LOGGER.warn("The exception was: {}", (Object)ioexception.toString()); ++ LOGGER.warn("Perhaps a server is already running on that port?"); ++ return false; ++ } ++ } ++ + Items.AIR.getSubItems(CreativeTabs.SEARCH, NonNullList.create()); + // <3 you Grum for this, saves us ~30 patch files! --^ + return net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStarting(this); +@@ -427,11 +475,19 @@ + + public void executePendingCommands() + { +- while (!this.pendingCommandList.isEmpty()) +- { +- PendingCommand pendingcommand = this.pendingCommandList.remove(0); +- this.getCommandManager().executeCommand(pendingcommand.sender, pendingcommand.command); ++ // Paper - use proper queue ++ PendingCommand pendingcommand; ++ while ((pendingcommand = this.pendingCommandList.poll()) != null) { ++ // Paper end ++ // CraftBukkit start - ServerCommand for preprocessing ++ ServerCommandEvent event = new ServerCommandEvent(console, pendingcommand.command); ++ server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) continue; ++ pendingcommand = new PendingCommand(event.getCommand(), pendingcommand.sender); ++ server.dispatchServerCommand(console, pendingcommand); ++ // CraftBukkit end + } ++ + } + + public boolean isDedicatedServer() +@@ -684,13 +740,65 @@ + + public String getPlugins() + { +- return ""; ++ // CraftBukkit start - Whole method ++ StringBuilder result = new StringBuilder(); ++ org.bukkit.plugin.Plugin[] plugins = server.getPluginManager().getPlugins(); ++ ++ result.append(server.getName()); ++ result.append(" on Bukkit "); ++ result.append(server.getBukkitVersion()); ++ ++ if (plugins.length > 0 && server.getQueryPlugins()) { ++ result.append(": "); ++ ++ for (int i = 0; i < plugins.length; i++) { ++ if (i > 0) { ++ result.append("; "); ++ } ++ ++ result.append(plugins[i].getDescription().getName()); ++ result.append(" "); ++ result.append(plugins[i].getDescription().getVersion().replaceAll(";", ",")); ++ } ++ } ++ ++ return result.toString(); ++ // CraftBukkit end + } + +- public String handleRConCommand(String command) ++ // CraftBukkit start - fire RemoteServerCommandEvent ++ public String handleRConCommand(final String command) + { +- this.rconConsoleSource.resetLog(); +- this.commandManager.executeCommand(this.rconConsoleSource, command); +- return this.rconConsoleSource.getLogContents(); ++ Waitable waitable = new Waitable() { ++ @Override ++ protected String evaluate() { ++ rconConsoleSource.resetLog(); ++ // Event changes start ++ RemoteServerCommandEvent event = new RemoteServerCommandEvent(remoteConsole, command); ++ server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return ""; ++ } ++ // Event change end ++ PendingCommand serverCommand = new PendingCommand(event.getCommand(), rconConsoleSource); ++ server.dispatchServerCommand(remoteConsole, serverCommand); ++ return rconConsoleSource.getLogContents(); ++ } ++ }; ++ processQueue.add(waitable); ++ try { ++ return waitable.get(); ++ } catch (java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException("Exception processing rcon command " + command, e.getCause()); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // Maintain interrupted state ++ throw new RuntimeException("Interrupted processing rcon command " + command, e); ++ } + } ++ // CraftBukkit end ++ ++ @Override ++ public PropertyManager getPropertyManager() { ++ return this.settings; ++ } + } diff --git a/patches/net/minecraft/server/dedicated/PropertyManager.java.patch b/patches/net/minecraft/server/dedicated/PropertyManager.java.patch new file mode 100644 index 00000000..d1e1d9c8 --- /dev/null +++ b/patches/net/minecraft/server/dedicated/PropertyManager.java.patch @@ -0,0 +1,109 @@ +--- ../src-base/minecraft/net/minecraft/server/dedicated/PropertyManager.java ++++ ../src-work/minecraft/net/minecraft/server/dedicated/PropertyManager.java +@@ -5,6 +5,8 @@ + import java.io.FileOutputStream; + import java.io.IOException; + import java.util.Properties; ++ ++import joptsimple.OptionSet; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; +@@ -14,8 +16,9 @@ + public class PropertyManager + { + private static final Logger LOGGER = LogManager.getLogger(); +- private final Properties serverProperties = new Properties(); ++ public final Properties serverProperties = new Properties(); + private final File serverPropertiesFile; ++ private OptionSet options = null; + + public PropertyManager(File propertiesFile) + { +@@ -57,6 +60,18 @@ + } + } + ++ public PropertyManager(final OptionSet options) { ++ this((File) options.valueOf("config")); ++ this.options = options; ++ } ++ ++ private T getOverride(String name, T value) { ++ if ((this.options != null) && (this.options.has(name))) { ++ return (T) this.options.valueOf(name); ++ } ++ return value; ++ } ++ + public void generateNewProperties() + { + LOGGER.info("Generating new properties file"); +@@ -69,6 +84,11 @@ + + try + { ++ // CraftBukkit start - Don't attempt writing to file if it's read only ++ if (this.serverPropertiesFile.exists() && !this.serverPropertiesFile.canWrite()) { ++ return; ++ } ++ // CraftBukkit end + fileoutputstream = new FileOutputStream(this.serverPropertiesFile); + this.serverProperties.store(fileoutputstream, "Minecraft server properties"); + } +@@ -107,20 +127,20 @@ + this.saveProperties(); + } + +- return this.serverProperties.getProperty(key, defaultValue); ++ return getOverride(key, this.serverProperties.getProperty(key, defaultValue)); + } + + public int getIntProperty(String key, int defaultValue) + { + try + { +- return Integer.parseInt(this.getStringProperty(key, "" + defaultValue)); ++ return getOverride(key, Integer.parseInt(this.getStringProperty(key, "" + defaultValue))); + } + catch (Exception var4) + { + this.serverProperties.setProperty(key, "" + defaultValue); + this.saveProperties(); +- return defaultValue; ++ return getOverride(key, defaultValue); + } + } + +@@ -128,13 +148,13 @@ + { + try + { +- return Long.parseLong(this.getStringProperty(key, "" + defaultValue)); ++ return getOverride(key, Long.parseLong(this.getStringProperty(key, "" + defaultValue))); + } + catch (Exception var5) + { + this.serverProperties.setProperty(key, "" + defaultValue); + this.saveProperties(); +- return defaultValue; ++ return getOverride(key, defaultValue); + } + } + +@@ -142,13 +162,13 @@ + { + try + { +- return Boolean.parseBoolean(this.getStringProperty(key, "" + defaultValue)); ++ return getOverride(key, Boolean.parseBoolean(this.getStringProperty(key, "" + defaultValue))); + } + catch (Exception var4) + { + this.serverProperties.setProperty(key, "" + defaultValue); + this.saveProperties(); +- return defaultValue; ++ return getOverride(key, defaultValue); + } + } + diff --git a/patches/net/minecraft/server/integrated/IntegratedServer.java.patch b/patches/net/minecraft/server/integrated/IntegratedServer.java.patch new file mode 100644 index 00000000..1033c099 --- /dev/null +++ b/patches/net/minecraft/server/integrated/IntegratedServer.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/server/integrated/IntegratedServer.java ++++ ../src-work/minecraft/net/minecraft/server/integrated/IntegratedServer.java +@@ -17,12 +17,14 @@ + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.profiler.Snooper; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.dedicated.PropertyManager; + import net.minecraft.server.management.PlayerProfileCache; + import net.minecraft.util.CryptManager; + import net.minecraft.util.HttpUtil; + import net.minecraft.util.Util; + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.GameType; ++import net.minecraft.world.MinecraftException; + import net.minecraft.world.ServerWorldEventHandler; + import net.minecraft.world.WorldServer; + import net.minecraft.world.WorldServerDemo; +@@ -48,7 +50,7 @@ + + public IntegratedServer(Minecraft clientIn, String folderNameIn, String worldNameIn, WorldSettings worldSettingsIn, YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) + { +- super(new File(clientIn.mcDataDir, "saves"), clientIn.getProxy(), clientIn.getDataFixer(), authServiceIn, sessionServiceIn, profileRepoIn, profileCacheIn); ++ super(null, clientIn.getProxy(), clientIn.getDataFixer(), authServiceIn, sessionServiceIn, profileRepoIn, profileCacheIn); + this.setServerOwner(clientIn.getSession().getUsername()); + this.setFolderName(folderNameIn); + this.setWorldName(worldNameIn); +@@ -60,6 +62,11 @@ + this.worldSettings = this.isDemo() ? WorldServerDemo.DEMO_WORLD_SETTINGS : worldSettingsIn; + } + ++ @Override ++ public PropertyManager getPropertyManager() { ++ return null; ++ } ++ + public ServerCommandManager createCommandManager() + { + return new IntegratedServerCommandManager(this); +@@ -369,7 +376,11 @@ + + public void stopServer() + { +- super.stopServer(); ++ try { ++ super.stopServer(); ++ } catch (MinecraftException e) { ++ e.printStackTrace(); ++ } + + if (this.lanServerPing != null) + { diff --git a/patches/net/minecraft/server/management/PlayerChunkMap.java.patch b/patches/net/minecraft/server/management/PlayerChunkMap.java.patch new file mode 100644 index 00000000..2d6731cf --- /dev/null +++ b/patches/net/minecraft/server/management/PlayerChunkMap.java.patch @@ -0,0 +1,263 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PlayerChunkMap.java ++++ ../src-work/minecraft/net/minecraft/server/management/PlayerChunkMap.java +@@ -5,14 +5,18 @@ + import com.google.common.collect.ComparisonChain; + import com.google.common.collect.Lists; + import com.google.common.collect.Sets; ++import io.netty.util.internal.ConcurrentSet; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import java.util.Collections; + import java.util.Comparator; + import java.util.Iterator; ++import java.util.LinkedList; + import java.util.List; + import java.util.Set; + import javax.annotation.Nullable; ++ ++import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.ChunkPos; +@@ -40,11 +44,12 @@ + private final WorldServer world; + private final List players = Lists.newArrayList(); + private final Long2ObjectMap entryMap = new Long2ObjectOpenHashMap(4096); +- private final Set dirtyEntries = Sets.newHashSet(); ++ private final Set dirtyEntries = new ConcurrentSet<>(); + private final List pendingSendToPlayers = Lists.newLinkedList(); + private final List entriesWithoutChunks = Lists.newLinkedList(); + private final List entries = Lists.newArrayList(); + private int playerViewRadius; ++ public int getViewDistance() { return playerViewRadius; } // Paper OBFHELPER + private long previousTotalWorldTime; + private boolean sortMissingChunks = true; + private boolean sortSendToPlayers = true; +@@ -52,7 +57,7 @@ + public PlayerChunkMap(WorldServer serverWorld) + { + this.world = serverWorld; +- this.setPlayerViewRadius(serverWorld.getMinecraftServer().getPlayerList().getViewDistance()); ++ this.setPlayerViewRadius(serverWorld.spigotConfig.viewDistance); // Spigot + } + + public WorldServer getWorldServer() +@@ -155,7 +160,7 @@ + + if (!this.entriesWithoutChunks.isEmpty()) + { +- long l = System.nanoTime() + 50000000L; ++ long l = System.nanoTime() + 25000000L; + int k = 49; + Iterator iterator = this.entriesWithoutChunks.iterator(); + +@@ -183,6 +188,9 @@ + break; + } + } ++ } else { ++ // CraftBukkit - SPIGOT-2891: remove once chunk has been provided ++ iterator.remove(); + } + } + } +@@ -213,7 +221,7 @@ + { + WorldProvider worldprovider = this.world.provider; + +- if (!worldprovider.canRespawnHere()) ++ if (!worldprovider.canRespawnHere() && !this.world.disableLevelSaving) // Paper - respect saving disabled setting + { + this.world.getChunkProvider().queueUnloadAll(); + } +@@ -257,6 +265,14 @@ + return playerchunkmapentry; + } + ++ public final boolean isChunkInUse(int x, int z) { ++ PlayerChunkMapEntry pi = getEntry(x, z); ++ if (pi != null) { ++ return (pi.players.size() > 0); ++ } ++ return false; ++ } ++ + public void markBlockForUpdate(BlockPos pos) + { + int i = pos.getX() >> 4; +@@ -276,14 +292,22 @@ + player.managedPosX = player.posX; + player.managedPosZ = player.posZ; + ++ List chunkList = new LinkedList<>(); ++ + for (int k = i - this.playerViewRadius; k <= i + this.playerViewRadius; ++k) + { + for (int l = j - this.playerViewRadius; l <= j + this.playerViewRadius; ++l) + { +- this.getOrCreateEntry(k, l).addPlayer(player); ++ // this.getOrCreateEntry(k, l).addPlayer(player); ++ chunkList.add(new ChunkPos(k, l)); + } + } + ++ Collections.sort(chunkList, new ChunkCoordComparator(player)); ++ for (ChunkPos pair : chunkList) { ++ this.getOrCreateEntry(pair.x, pair.z).addPlayer(player); ++ } ++ + this.players.add(player); + this.markSortPending(); + } +@@ -341,6 +365,8 @@ + int j1 = i - k; + int k1 = j - l; + ++ List chunksToLoad = new LinkedList<>(); ++ + if (j1 != 0 || k1 != 0) + { + for (int l1 = i - i1; l1 <= i + i1; ++l1) +@@ -349,7 +375,8 @@ + { + if (!this.overlaps(l1, i2, k, l, i1)) + { +- this.getOrCreateEntry(l1, i2).addPlayer(player); ++ // this.getOrCreateEntry(l1, i2).addPlayer(player); ++ chunksToLoad.add(new ChunkPos(l1, i2)); + } + + if (!this.overlaps(l1 - j1, i2 - k1, i, j, i1)) +@@ -367,6 +394,12 @@ + player.managedPosX = player.posX; + player.managedPosZ = player.posZ; + this.markSortPending(); ++ // CraftBukkit start - send nearest chunks first ++ Collections.sort(chunksToLoad, new ChunkCoordComparator(player)); ++ for (ChunkPos pair : chunksToLoad) { ++ this.getOrCreateEntry(pair.x, pair.z).addPlayer(player); ++ } ++ // CraftBukkit end + } + } + } +@@ -425,6 +458,45 @@ + } + } + ++ public void setViewDistance(EntityPlayerMP entityplayer, int i) { ++ this.setViewDistance(entityplayer, i, true); ++ } ++ ++ public void setViewDistance(EntityPlayerMP entityplayer, int i, boolean markSort) { ++ i = MathHelper.clamp(i, 3, 32); ++ int oldViewDistance = entityplayer.getViewDistance(); ++ if (i != oldViewDistance) { ++ int j = i - oldViewDistance; ++ int k = (int)entityplayer.posX >> 4; ++ int l = (int)entityplayer.posZ >> 4; ++ int i1; ++ int j1; ++ if (j > 0) { ++ for(i1 = k - i; i1 <= k + i; ++i1) { ++ for(j1 = l - i; j1 <= l + i; ++j1) { ++ PlayerChunkMapEntry playerchunk = this.getOrCreateEntry(i1, j1); ++ if (!playerchunk.containsPlayer(entityplayer)) { ++ playerchunk.addPlayer(entityplayer); ++ } ++ } ++ } ++ } else { ++ for(i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) { ++ for(j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) { ++ if (!this.overlaps(i1, j1, k, l, i)) { ++ this.getOrCreateEntry(i1, j1).removePlayer(entityplayer); ++ } ++ } ++ } ++ ++ if (markSort) { ++ this.markSortPending(); ++ } ++ } ++ } ++ ++ } ++ + private void markSortPending() + { + this.sortMissingChunks = true; +@@ -446,6 +518,20 @@ + this.dirtyEntries.add(entry); + } + ++ public void updateViewDistance(EntityPlayerMP player, final int distanceIn) { ++ int oldViewDistance = player.getViewDistance(); ++ int toSet; ++ int playerViewDistance = toSet = MathHelper.clamp(distanceIn, 3, 32); ++ if (distanceIn < 0) { ++ playerViewDistance = -1; ++ toSet = this.world.getPlayerChunkMap().getViewDistance(); ++ } ++ if (toSet != oldViewDistance) { ++ this.setViewDistance(player, toSet); ++ player.setViewDistance(playerViewDistance); ++ } ++ } ++ + public void removeEntry(PlayerChunkMapEntry entry) + { + ChunkPos chunkpos = entry.getPos(); +@@ -456,11 +542,48 @@ + this.dirtyEntries.remove(entry); + this.pendingSendToPlayers.remove(entry); + this.entriesWithoutChunks.remove(entry); +- Chunk chunk = entry.getChunk(); ++ } + +- if (chunk != null) +- { +- this.getWorldServer().getChunkProvider().queueUnload(chunk); ++ // CraftBukkit start - Sorter to load nearby chunks first ++ private static class ChunkCoordComparator implements java.util.Comparator { ++ private int x; ++ private int z; ++ ++ public ChunkCoordComparator (EntityPlayer entityplayer) { ++ x = (int) entityplayer.posX >> 4; ++ z = (int) entityplayer.posZ >> 4; + } ++ ++ public int compare(ChunkPos a, ChunkPos b) { ++ if (a.equals(b)) { ++ return 0; ++ } ++ ++ // Subtract current position to set center point ++ int ax = a.x - this.x; ++ int az = a.z - this.z; ++ int bx = b.x - this.x; ++ int bz = b.z - this.z; ++ ++ int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz)); ++ if (result != 0) { ++ return result; ++ } ++ ++ if (ax < 0) { ++ if (bx < 0) { ++ return bz - az; ++ } else { ++ return -1; ++ } ++ } else { ++ if (bx < 0) { ++ return 1; ++ } else { ++ return az - bz; ++ } ++ } ++ } + } ++ // CraftBukkit end + } diff --git a/patches/net/minecraft/server/management/PlayerChunkMapEntry.java.patch b/patches/net/minecraft/server/management/PlayerChunkMapEntry.java.patch new file mode 100644 index 00000000..7456335f --- /dev/null +++ b/patches/net/minecraft/server/management/PlayerChunkMapEntry.java.patch @@ -0,0 +1,16 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java ++++ ../src-work/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java +@@ -23,11 +23,11 @@ + { + private static final Logger LOGGER = LogManager.getLogger(); + private final PlayerChunkMap playerChunkMap; +- private final List players = Lists.newArrayList(); ++ public final List players = Lists.newArrayList(); + private final ChunkPos pos; + private short[] changedBlocks = new short[64]; + @Nullable +- private Chunk chunk; ++ public Chunk chunk; + private int changes; + private int changedSectionFilter; + private long lastUpdateInhabitedTime; diff --git a/patches/net/minecraft/server/management/PlayerInteractionManager.java.patch b/patches/net/minecraft/server/management/PlayerInteractionManager.java.patch new file mode 100644 index 00000000..00fc1c82 --- /dev/null +++ b/patches/net/minecraft/server/management/PlayerInteractionManager.java.patch @@ -0,0 +1,256 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PlayerInteractionManager.java ++++ ../src-work/minecraft/net/minecraft/server/management/PlayerInteractionManager.java +@@ -1,19 +1,23 @@ + package net.minecraft.server.management; + +-import net.minecraft.block.Block; +-import net.minecraft.block.BlockChest; +-import net.minecraft.block.BlockCommandBlock; +-import net.minecraft.block.BlockStructure; ++import net.minecraft.block.*; + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; ++import net.minecraft.enchantment.EnchantmentHelper; ++import net.minecraft.entity.item.EntityItem; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; ++import net.minecraft.init.Blocks; ++import net.minecraft.init.Enchantments; ++import net.minecraft.inventory.EntityEquipmentSlot; + import net.minecraft.inventory.IInventory; + import net.minecraft.item.ItemBlock; + import net.minecraft.item.ItemStack; + import net.minecraft.item.ItemSword; + import net.minecraft.network.play.server.SPacketBlockChange; ++import net.minecraft.network.play.server.SPacketCloseWindow; + import net.minecraft.network.play.server.SPacketPlayerListItem; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.tileentity.TileEntity; + import net.minecraft.tileentity.TileEntityChest; + import net.minecraft.util.ActionResult; +@@ -25,7 +29,14 @@ + import net.minecraft.world.ILockableContainer; + import net.minecraft.world.World; + import net.minecraft.world.WorldServer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.Event; ++import org.bukkit.event.block.Action; ++import org.bukkit.event.block.BlockBreakEvent; ++import org.bukkit.event.player.PlayerInteractEvent; + ++import java.util.ArrayList; ++ + public class PlayerInteractionManager + { + public World world; +@@ -50,7 +61,7 @@ + this.gameType = type; + type.configurePlayerCapabilities(this.player.capabilities); + this.player.sendPlayerAbilities(); +- this.player.mcServer.getPlayerList().sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_GAME_MODE, new EntityPlayerMP[] {this.player})); ++ this.player.mcServer.getPlayerList().sendAll(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_GAME_MODE, new EntityPlayerMP[] {this.player}), this.player); // CraftBukkit + this.world.updateAllPlayersSleepingFlag(); + } + +@@ -81,7 +92,7 @@ + + public void updateBlockRemoving() + { +- ++this.curblockDamage; ++ this.curblockDamage = MinecraftServer.currentTick; // CraftBukkit + + if (this.receivedFinishDiggingPacket) + { +@@ -137,6 +148,19 @@ + + public void onBlockClicked(BlockPos pos, EnumFacing side) + { ++ // CraftBukkit start ++ PlayerInteractEvent playerinteractevent = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, side, this.player.inventory.getCurrentItem(), EnumHand.MAIN_HAND); ++ if (playerinteractevent.isCancelled()) { ++ // Let the client know the block still exists ++ this.player.connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ // Update any tile entity data for this block ++ TileEntity tileentity = this.world.getTileEntity(pos); ++ if (tileentity != null) { ++ this.player.connection.sendPacket(tileentity.getUpdatePacket()); ++ } ++ return; ++ } ++ // CraftBukkit end + double reachDist = player.getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue(); + net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickBlock event = net.minecraftforge.common.ForgeHooks.onLeftClickBlock(player, pos, side, net.minecraftforge.common.ForgeHooks.rayTraceEyeHitVec(player, reachDist + 1)); + if (event.isCanceled()) +@@ -185,11 +209,24 @@ + this.initialDamage = this.curblockDamage; + float f = 1.0F; + +- if (!iblockstate.getBlock().isAir(iblockstate, world, pos)) +- { ++ // CraftBukkit start - Swings at air do *NOT* exist. ++ if (playerinteractevent.useInteractedBlock() == Event.Result.DENY) { ++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. ++ IBlockState data = this.world.getBlockState(pos); ++ if (block == Blocks.OAK_DOOR) { ++ // For some reason *BOTH* the bottom/top part have to be marked updated. ++ boolean bottom = data.getValue(BlockDoor.HALF) == BlockDoor.EnumDoorHalf.LOWER; ++ ((EntityPlayerMP) this.player).connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ ((EntityPlayerMP) this.player).connection.sendPacket(new SPacketBlockChange(this.world, bottom ? pos.up() : pos.down())); ++ } else if (block == Blocks.TRAPDOOR) { ++ ++ ((EntityPlayerMP) this.player).connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ } ++ } else if (iblockstate.getMaterial() != Material.AIR) { + if (event.getUseBlock() != net.minecraftforge.fml.common.eventhandler.Event.Result.DENY) + { + block.onBlockClicked(this.world, pos, this.player); ++ // Allow fire punching to be blocked + this.world.extinguishFire((EntityPlayer)null, pos, side); + } + else +@@ -200,6 +237,26 @@ + } + f = iblockstate.getPlayerRelativeBlockHardness(this.player, this.player.world, pos); + } ++ if (playerinteractevent.useItemInHand() == Event.Result.DENY) { ++ // If we 'insta destroyed' then the client needs to be informed. ++ if (f > 1.0f) { ++ ((EntityPlayerMP) this.player).connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ } ++ return; ++ } ++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos.getX(), pos.getY(), pos.getZ(), this.player.inventory.getCurrentItem(), f >= 1.0f); ++ ++ if (blockEvent.isCancelled()) { ++ // Let the client know the block still exists ++ ((EntityPlayerMP) this.player).connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ return; ++ } ++ ++ if (blockEvent.getInstaBreak()) { ++ f = 2.0f; ++ } ++ // CraftBukkit end ++ + if (event.getUseItem() == net.minecraftforge.fml.common.eventhandler.Event.Result.DENY) + { + if (f >= 1.0F) +@@ -221,6 +278,7 @@ + this.destroyPos = pos; + int i = (int)(f * 10.0F); + this.world.sendBlockBreakProgress(this.player.getEntityId(), pos, i); ++ this.player.connection.sendPacket(new SPacketBlockChange(world, pos)); // Paper - MC-54026 - backport from 1.13 + this.durabilityRemainingOnBlock = i; + } + } +@@ -230,6 +288,7 @@ + { + if (pos.equals(this.destroyPos)) + { ++ this.curblockDamage = MinecraftServer.currentTick; // CraftBukkit + int i = this.curblockDamage - this.initialDamage; + IBlockState iblockstate = this.world.getBlockState(pos); + +@@ -251,6 +310,15 @@ + this.initialBlockDamage = this.initialDamage; + } + } ++ } else { ++ // CraftBukkit start - Force block reset to client ++ this.player.connection.sendPacket(new SPacketBlockChange(this.world, pos)); ++ // CraftBukkit end ++ // update TE for this block ++ TileEntity tileentity = this.world.getTileEntity(pos); ++ if (tileentity != null) { ++ this.player.connection.sendPacket(tileentity.getUpdatePacket()); ++ } + } + } + +@@ -331,7 +399,7 @@ + // Drop experience + if (!this.isCreative() && flag1 && exp > 0) + { +- iblockstate.getBlock().dropXpOnBlockBreak(world, pos, exp); ++ iblockstate.getBlock().dropXpOnBlockBreak(world, pos, exp, this.player); + } + return flag1; + } +@@ -396,9 +464,58 @@ + } + } + ++ public boolean interactResult = false; ++ public boolean firedInteract = false; ++ + public EnumActionResult processRightClickBlock(EntityPlayer player, World worldIn, ItemStack stack, EnumHand hand, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ) + { +- if (this.gameType == GameType.SPECTATOR) ++ IBlockState blockdata = worldIn.getBlockState(pos); ++ ++ if (blockdata.getBlock() != Blocks.AIR) { ++ boolean cancelledBlock = false; ++ ++ if (this.gameType == GameType.SPECTATOR) { ++ TileEntity tileentity = worldIn.getTileEntity(pos); ++ cancelledBlock = !(tileentity instanceof ILockableContainer || tileentity instanceof IInventory); ++ } ++ ++ if (!player.getBukkitEntity().isOp() && stack != null && Block.getBlockFromItem(stack.getItem()) instanceof BlockCommandBlock) { ++ cancelledBlock = true; ++ } ++ ++ ++ PlayerInteractEvent playerInteractEvent = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, pos, facing, stack, cancelledBlock, hand); ++ firedInteract = true; ++ interactResult = playerInteractEvent.useItemInHand() == Event.Result.DENY; ++ ++ if (playerInteractEvent.useInteractedBlock() == Event.Result.DENY) { ++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. ++ if (blockdata.getBlock() instanceof BlockDoor) { ++ boolean bottom = blockdata.getValue(BlockDoor.HALF) == BlockDoor.EnumDoorHalf.LOWER; ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketBlockChange(worldIn, bottom ? pos.up() : pos.down())); ++ } else if (blockdata.getBlock() instanceof BlockCake) { ++ ((EntityPlayerMP) player).getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake ++ // Paper start - extend Player Interact cancellation ++ } else if (blockdata.getBlock() instanceof BlockStructure) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketCloseWindow()); ++ } else if (blockdata.getBlock() instanceof BlockCommandBlock) { ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketCloseWindow()); ++ } else if (blockdata.getBlock() instanceof BlockFlowerPot) { ++ // Send a block change to air and then send back the correct block, just to make the client happy ++ SPacketBlockChange packet = new SPacketBlockChange(worldIn, pos); ++ packet.blockState = Blocks.AIR.getDefaultState(); ++ this.player.connection.sendPacket(packet); ++ this.player.connection.sendPacket(new SPacketBlockChange(worldIn, pos)); ++ TileEntity tileentity = worldIn.getTileEntity(pos); ++ if (tileentity != null) { ++ ((EntityPlayerMP) player).connection.sendPacket(tileentity.getUpdatePacket()); ++ } ++ // Paper end - extend Player Interact cancellation ++ } ++ ((EntityPlayerMP) player).getBukkitEntity().updateInventory(); // SPIGOT-2867 ++ return (playerInteractEvent.useItemInHand() != Event.Result.ALLOW) ? EnumActionResult.SUCCESS : EnumActionResult.PASS; ++ } ++ else if (this.gameType == GameType.SPECTATOR) + { + TileEntity tileentity = worldIn.getTileEntity(pos); + +@@ -460,7 +577,7 @@ + { + return EnumActionResult.PASS; + } +- else ++ else if (!interactResult) + { + if (stack.getItem() instanceof ItemBlock && !player.canUseCommandBlock()) + { +@@ -496,6 +613,8 @@ + } + } + } ++ return EnumActionResult.FAIL; ++ } + + public void setWorld(WorldServer serverWorld) + { diff --git a/patches/net/minecraft/server/management/PlayerList.java.patch b/patches/net/minecraft/server/management/PlayerList.java.patch new file mode 100644 index 00000000..41bb44ae --- /dev/null +++ b/patches/net/minecraft/server/management/PlayerList.java.patch @@ -0,0 +1,1127 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PlayerList.java ++++ ../src-work/minecraft/net/minecraft/server/management/PlayerList.java +@@ -1,5 +1,8 @@ + package net.minecraft.server.management; + ++import com.destroystokyo.paper.MCUtil; ++import com.google.common.base.Predicate; ++import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -8,6 +11,8 @@ + import java.io.File; + import java.net.SocketAddress; + import java.text.SimpleDateFormat; ++import java.util.ArrayList; ++import java.util.Iterator; + import java.util.List; + import java.util.Map; + import java.util.Set; +@@ -27,6 +32,7 @@ + import net.minecraft.network.play.server.SPacketChat; + import net.minecraft.network.play.server.SPacketCustomPayload; + import net.minecraft.network.play.server.SPacketEntityEffect; ++import net.minecraft.network.play.server.SPacketEntityMetadata; + import net.minecraft.network.play.server.SPacketEntityStatus; + import net.minecraft.network.play.server.SPacketHeldItemChange; + import net.minecraft.network.play.server.SPacketJoinGame; +@@ -45,6 +51,7 @@ + import net.minecraft.scoreboard.ServerScoreboard; + import net.minecraft.scoreboard.Team; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.network.NetHandlerLoginServer; + import net.minecraft.stats.StatList; + import net.minecraft.stats.StatisticsManagerServer; + import net.minecraft.util.math.BlockPos; +@@ -52,7 +59,7 @@ + import net.minecraft.util.text.ChatType; + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentTranslation; +-import net.minecraft.util.text.TextFormatting; ++import net.minecraft.util.text.translation.I18n; + import net.minecraft.world.DimensionType; + import net.minecraft.world.GameType; + import net.minecraft.world.World; +@@ -62,10 +69,35 @@ + import net.minecraft.world.chunk.storage.AnvilChunkLoader; + import net.minecraft.world.storage.IPlayerFileData; + import net.minecraft.world.storage.WorldInfo; ++import net.minecraftforge.common.DimensionManager; ++import net.minecraftforge.common.network.ForgeMessage; ++import net.minecraftforge.common.network.ForgeMessage.DimensionRegisterMessage; ++import net.minecraftforge.common.network.ForgeNetworkHandler; ++import net.minecraftforge.fml.common.network.FMLEmbeddedChannel; ++import net.minecraftforge.fml.common.network.FMLOutboundHandler; ++import net.minecraftforge.fml.common.network.FMLOutboundHandler.OutboundTarget; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.TravelAgent; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor; ++import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerChangedWorldEvent; ++import org.bukkit.event.player.PlayerJoinEvent; ++import org.bukkit.event.player.PlayerLoginEvent; ++import org.bukkit.event.player.PlayerPortalEvent; ++import org.bukkit.event.player.PlayerQuitEvent; ++import org.bukkit.event.player.PlayerRespawnEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.util.Vector; ++import org.spigotmc.event.player.PlayerSpawnLocationEvent; + + public abstract class PlayerList + { +@@ -76,15 +108,15 @@ + private static final Logger LOGGER = LogManager.getLogger(); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); + private final MinecraftServer mcServer; +- private final List playerEntityList = Lists.newArrayList(); ++ public final List playerEntityList = new java.util.concurrent.CopyOnWriteArrayList<>(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety + private final Map uuidToPlayerMap = Maps.newHashMap(); + private final UserListBans bannedPlayers; + private final UserListIPBans bannedIPs; + private final UserListOps ops; + private final UserListWhitelist whiteListedPlayers; +- private final Map playerStatFiles; +- private final Map advancements; +- private IPlayerFileData playerDataManager; ++ // private final Map playerStatFiles; ++ // private final Map advancements; ++ public IPlayerFileData playerDataManager; + private boolean whiteListEnforced; + protected int maxPlayers; + private int viewDistance; +@@ -92,14 +124,19 @@ + private boolean commandsAllowedForAll; + private int playerPingIndex; + ++ private CraftServer cserver; ++ + public PlayerList(MinecraftServer server) + { ++ this.cserver = server.server = new CraftServer(server, this); ++ server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance(); ++ server.reader.addCompleter(new ConsoleCommandCompleter(server.server)); + this.bannedPlayers = new UserListBans(FILE_PLAYERBANS); + this.bannedIPs = new UserListIPBans(FILE_IPBANS); + this.ops = new UserListOps(FILE_OPS); + this.whiteListedPlayers = new UserListWhitelist(FILE_WHITELIST); +- this.playerStatFiles = Maps.newHashMap(); +- this.advancements = Maps.newHashMap(); ++ // this.playerStatFiles = Maps.newHashMap(); ++ // this.advancements = Maps.newHashMap(); + this.mcServer = server; + this.bannedPlayers.setLanServer(false); + this.bannedIPs.setLanServer(false); +@@ -125,6 +162,13 @@ + playerIn.setPosition(spawnPoint.getX(), spawnPoint.getY(), spawnPoint.getZ()); + } + ++ // CraftBukkit start - Better rename detection ++ if (nbttagcompound != null && nbttagcompound.hasKey("bukkit")) { ++ NBTTagCompound bukkit = nbttagcompound.getCompoundTag("bukkit"); ++ s = bukkit.hasKey("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; ++ } ++ // CraftBukkit end ++ + playerIn.setWorld(playerWorld); + playerIn.interactionManager.setWorld((WorldServer)playerIn.world); + String s1 = "local"; +@@ -134,36 +178,61 @@ + s1 = netManager.getRemoteAddress().toString(); + } + +- LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", playerIn.getName(), s1, Integer.valueOf(playerIn.getEntityId()), Double.valueOf(playerIn.posX), Double.valueOf(playerIn.posY), Double.valueOf(playerIn.posZ)); ++ // Spigot start - spawn location event ++ Player bukkitPlayer = playerIn.getBukkitEntity(); ++ PlayerSpawnLocationEvent ev = new PlayerSpawnLocationEvent(bukkitPlayer, bukkitPlayer.getLocation()); ++ Bukkit.getPluginManager().callEvent(ev); ++ ++ Location loc = ev.getSpawnLocation(); ++ WorldServer world = ((CraftWorld) loc.getWorld()).getHandle(); ++ ++ playerIn.setWorld(world); ++ playerIn.setPositionAndRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); ++ // Spigot end ++ ++ // CraftBukkit - Moved message to after join ++ // LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", playerIn.getName(), s1, Integer.valueOf(playerIn.getEntityId()), Double.valueOf(playerIn.posX), Double.valueOf(playerIn.posY), Double.valueOf(playerIn.posZ)); + WorldServer worldserver = this.mcServer.getWorld(playerIn.dimension); + WorldInfo worldinfo = worldserver.getWorldInfo(); + this.setPlayerGameTypeBasedOnOther(playerIn, (EntityPlayerMP)null, worldserver); + playerIn.connection = nethandlerplayserver; ++ if (DimensionManager.isBukkitDimension(playerIn.dimension)) ++ { ++ FMLEmbeddedChannel serverChannel = ForgeNetworkHandler.getServerChannel(); ++ serverChannel.attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.PLAYER); ++ serverChannel.attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(playerIn); ++ serverChannel.writeOutbound(new ForgeMessage.DimensionRegisterMessage(playerIn.dimension, DimensionManager.getProviderType(playerIn.dimension).name())); ++ } + net.minecraftforge.fml.common.FMLCommonHandler.instance().fireServerConnectionEvent(netManager); + nethandlerplayserver.sendPacket(new SPacketJoinGame(playerIn.getEntityId(), playerIn.interactionManager.getGameType(), worldinfo.isHardcoreModeEnabled(), worldserver.provider.getDimension(), worldserver.getDifficulty(), this.getMaxPlayers(), worldinfo.getTerrainType(), worldserver.getGameRules().getBoolean("reducedDebugInfo"))); ++ playerIn.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + nethandlerplayserver.sendPacket(new SPacketCustomPayload("MC|Brand", (new PacketBuffer(Unpooled.buffer())).writeString(this.getServerInstance().getServerModName()))); + nethandlerplayserver.sendPacket(new SPacketServerDifficulty(worldinfo.getDifficulty(), worldinfo.isDifficultyLocked())); + nethandlerplayserver.sendPacket(new SPacketPlayerAbilities(playerIn.capabilities)); + nethandlerplayserver.sendPacket(new SPacketHeldItemChange(playerIn.inventory.currentItem)); ++ nethandlerplayserver.sendPacket(new SPacketEntityStatus(playerIn, (byte) (worldserver.getGameRules().getBoolean("reducedDebugInfo") ? 22 : 23))); // Paper - fix this rule not being initialized on the client + this.updatePermissionLevel(playerIn); + playerIn.getStatFile().markAllDirty(); + playerIn.getRecipeBook().init(playerIn); + this.sendScoreboard((ServerScoreboard)worldserver.getScoreboard(), playerIn); + this.mcServer.refreshStatusNextTick(); +- TextComponentTranslation textcomponenttranslation; +- ++ // CraftBukkit start - login message is handled in the event ++ // TextComponentTranslation textcomponenttranslation; ++ String joinMessage; + if (playerIn.getName().equalsIgnoreCase(s)) + { +- textcomponenttranslation = new TextComponentTranslation("multiplayer.player.joined", new Object[] {playerIn.getDisplayName()}); ++ // textcomponenttranslation = new TextComponentTranslation("multiplayer.player.joined", new Object[] {playerIn.getDisplayName()}); ++ joinMessage = "\u00A7e" + I18n.translateToLocalFormatted("multiplayer.player.joined", playerIn.getName()); + } + else + { +- textcomponenttranslation = new TextComponentTranslation("multiplayer.player.joined.renamed", new Object[] {playerIn.getDisplayName(), s}); ++ // textcomponenttranslation = new TextComponentTranslation("multiplayer.player.joined.renamed", new Object[] {playerIn.getDisplayName(), s}); ++ joinMessage = "\u00A7e" + I18n.translateToLocalFormatted("multiplayer.player.joined.renamed", playerIn.getName(), s); + } + +- textcomponenttranslation.getStyle().setColor(TextFormatting.YELLOW); +- this.sendMessage(textcomponenttranslation); +- this.playerLoggedIn(playerIn); ++ // textcomponenttranslation.getStyle().setColor(TextFormatting.YELLOW); ++ // this.sendMessage(textcomponenttranslation); ++ this.playerLoggedIn(playerIn, joinMessage); + nethandlerplayserver.setPlayerLocation(playerIn.posX, playerIn.posY, playerIn.posZ, playerIn.rotationYaw, playerIn.rotationPitch); + this.updateTimeAndWeatherForPlayer(playerIn, worldserver); + +@@ -217,9 +286,11 @@ + + playerIn.addSelfToInternalCraftingInventory(); + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerLoggedIn(playerIn); ++ // CraftBukkit - Moved from above, added world ++ PlayerList.LOGGER.info(playerIn.getName() + "[" + s1 + "] logged in with entity id " + playerIn.getEntityId() + " at ([" + playerIn.world.worldInfo.getWorldName() + "]" + playerIn.posX + ", " + playerIn.posY + ", " + playerIn.posZ + ")"); + } + +- protected void sendScoreboard(ServerScoreboard scoreboardIn, EntityPlayerMP playerIn) ++ public void sendScoreboard(ServerScoreboard scoreboardIn, EntityPlayerMP playerIn) + { + Set set = Sets.newHashSet(); + +@@ -246,28 +317,29 @@ + + public void setPlayerManager(WorldServer[] worldServers) + { ++ if (playerDataManager != null) return; + this.playerDataManager = worldServers[0].getSaveHandler().getPlayerNBTManager(); + worldServers[0].getWorldBorder().addListener(new IBorderListener() + { + public void onSizeChanged(WorldBorder border, double newSize) + { +- PlayerList.this.sendPacketToAllPlayers(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_SIZE)); ++ PlayerList.this.sendAll(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_SIZE), border.world); + } + public void onTransitionStarted(WorldBorder border, double oldSize, double newSize, long time) + { +- PlayerList.this.sendPacketToAllPlayers(new SPacketWorldBorder(border, SPacketWorldBorder.Action.LERP_SIZE)); ++ PlayerList.this.sendAll(new SPacketWorldBorder(border, SPacketWorldBorder.Action.LERP_SIZE), border.world); + } + public void onCenterChanged(WorldBorder border, double x, double z) + { +- PlayerList.this.sendPacketToAllPlayers(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_CENTER)); ++ PlayerList.this.sendAll(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_CENTER), border.world); + } + public void onWarningTimeChanged(WorldBorder border, int newTime) + { +- PlayerList.this.sendPacketToAllPlayers(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_WARNING_TIME)); ++ PlayerList.this.sendAll(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_WARNING_TIME), border.world); + } + public void onWarningDistanceChanged(WorldBorder border, int newDistance) + { +- PlayerList.this.sendPacketToAllPlayers(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_WARNING_BLOCKS)); ++ PlayerList.this.sendAll(new SPacketWorldBorder(border, SPacketWorldBorder.Action.SET_WARNING_BLOCKS), border.world); + } + public void onDamageAmountChanged(WorldBorder border, double newAmount) + { +@@ -344,16 +416,16 @@ + protected void writePlayerData(EntityPlayerMP playerIn) + { + if (playerIn.connection == null) return; +- ++ playerIn.lastSave = MinecraftServer.currentTick; // Paper + this.playerDataManager.writePlayerData(playerIn); +- StatisticsManagerServer statisticsmanagerserver = this.playerStatFiles.get(playerIn.getUniqueID()); ++ StatisticsManagerServer statisticsmanagerserver = playerIn.getStatFile(); + + if (statisticsmanagerserver != null) + { + statisticsmanagerserver.saveStatFile(); + } + +- PlayerAdvancements playeradvancements = this.advancements.get(playerIn.getUniqueID()); ++ PlayerAdvancements playeradvancements = playerIn.getAdvancements(); + + if (playeradvancements != null) + { +@@ -378,16 +450,84 @@ + this.preparePlayer(playerIn, (WorldServer)null); + } + ++ public void playerLoggedIn(EntityPlayerMP playerIn, String joinMessage) ++ { ++ this.playerEntityList.add(playerIn); ++ this.uuidToPlayerMap.put(playerIn.getUniqueID(), playerIn); ++ // this.sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.ADD_PLAYER, new EntityPlayerMP[] {playerIn})); // CraftBukkit - replaced with loop below ++ WorldServer worldserver = this.mcServer.getWorld(playerIn.dimension); ++ ++ PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(cserver.getPlayer(playerIn), joinMessage); ++ Bukkit.getPluginManager().callEvent(playerJoinEvent); ++ ++ if (!playerIn.connection.netManager.isChannelOpen()) { ++ return; ++ } ++ ++ joinMessage = playerJoinEvent.getJoinMessage(); ++ ++ if (joinMessage != null && joinMessage.length() > 0) { ++ for (ITextComponent line : org.bukkit.craftbukkit.util.CraftChatMessage.fromString(joinMessage)) { ++ mcServer.getPlayerList().sendPacketToAllPlayers(new SPacketChat(line)); ++ } ++ } ++ ++ ChunkIOExecutor.adjustPoolSize(getCurrentPlayerCount()); ++ ++ // CraftBukkit start - sendAll above replaced with this loop ++ SPacketPlayerListItem packet = new SPacketPlayerListItem(SPacketPlayerListItem.Action.ADD_PLAYER, playerIn); ++ for (int i = 0; i < this.playerEntityList.size(); ++i) ++ { ++ // playerIn.connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.ADD_PLAYER, new EntityPlayerMP[] {this.playerEntityList.get(i)})); ++ EntityPlayerMP entityplayer1 = this.playerEntityList.get(i); ++ ++ if (entityplayer1.getBukkitEntity().canSee(playerIn.getBukkitEntity())) { ++ entityplayer1.connection.sendPacket(packet); ++ } ++ ++ if (!playerIn.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { ++ continue; ++ } ++ ++ playerIn.connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.ADD_PLAYER, new EntityPlayerMP[] { entityplayer1})); ++ } ++ playerIn.sentListPacket = true; ++ // CraftBukkit end ++ ++ playerIn.connection.sendPacket(new SPacketEntityMetadata(playerIn.getEntityId(), playerIn.getDataManager(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn ++ net.minecraftforge.common.chunkio.ChunkIOExecutor.adjustPoolSize(this.getCurrentPlayerCount()); ++ // CraftBukkit start - Only add if the player wasn't moved in the event ++ if (playerIn.world == worldserver && !worldserver.playerEntities.contains(playerIn)) { ++ worldserver.spawnEntity(playerIn); ++ this.preparePlayer(playerIn, null); ++ } ++ // CraftBukkit end ++ } ++ + public void serverUpdateMovingPlayer(EntityPlayerMP playerIn) + { + playerIn.getServerWorld().getPlayerChunkMap().updateMovingPlayer(playerIn); + } + +- public void playerLoggedOut(EntityPlayerMP playerIn) ++ public String playerLoggedOut(EntityPlayerMP playerIn) + { ++ String quitMessage = null; + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerLoggedOut(playerIn); + WorldServer worldserver = playerIn.getServerWorld(); + playerIn.addStat(StatList.LEAVE_GAME); ++ ++ // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(playerIn); ++ if(playerIn.connection != null) { ++ // Kettle CraftServer Changed to Bukkit ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(playerIn), "\u00A7e" + playerIn.getName() + " left the game"); ++ Bukkit.getPluginManager().callEvent(playerQuitEvent); ++ playerIn.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); ++ ++ playerIn.onUpdateEntity();// SPIGOT-924 ++ quitMessage = playerQuitEvent.getQuitMessage(); ++ } ++ // CraftBukkit end + this.writePlayerData(playerIn); + + if (playerIn.isRiding()) +@@ -420,11 +560,27 @@ + if (entityplayermp == playerIn) + { + this.uuidToPlayerMap.remove(uuid); +- this.playerStatFiles.remove(uuid); +- this.advancements.remove(uuid); ++ // this.playerStatFiles.remove(uuid); ++ // this.advancements.remove(uuid); + } + +- this.sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.REMOVE_PLAYER, new EntityPlayerMP[] {playerIn})); ++ // this.sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.REMOVE_PLAYER, new EntityPlayerMP[] {playerIn})); ++ SPacketPlayerListItem packet = new SPacketPlayerListItem(SPacketPlayerListItem.Action.REMOVE_PLAYER, playerIn); ++ for (int i = 0; i < playerEntityList.size(); i++) { ++ EntityPlayerMP entityplayer2 = (EntityPlayerMP) this.playerEntityList.get(i); ++ ++ if (entityplayer2.getBukkitEntity().canSee(playerIn.getBukkitEntity())) { ++ entityplayer2.connection.sendPacket(packet); ++ } else { ++ entityplayer2.getBukkitEntity().removeDisconnectingPlayer(playerIn.getBukkitEntity()); ++ } ++ } ++ // This removes the scoreboard (and player reference) for the specific player in the manager ++ cserver.getScoreboardManager().removePlayer(playerIn.getBukkitEntity()); ++ ++ ChunkIOExecutor.adjustPoolSize(this.getCurrentPlayerCount()); ++ ++ return quitMessage; + } + + public String allowUserToConnect(SocketAddress address, GameProfile profile) +@@ -463,6 +619,72 @@ + } + } + ++ @Nullable ++ public EntityPlayerMP allowUserToConnect(NetHandlerLoginServer loginServer, GameProfile profile, String hostname) ++ { ++ UUID uuid = EntityPlayer.getUUID(profile); ++ for (EntityPlayerMP onlinePlayer : this.playerEntityList) { ++ if (onlinePlayer.getUniqueID().equals(uuid)) { ++ cserver.getPlayer(uuid).kickPlayer("You are online, you cannot login repeatedly!"); ++ } ++ } ++ ++ // Instead of kicking then returning, we need to store the kick reason ++ // in the event, check with plugins to see if it's ok, and THEN kick ++ // depending on the outcome. ++ SocketAddress socketaddress = loginServer.networkManager.getRemoteAddress(); ++ ++ EntityPlayerMP entity = new EntityPlayerMP(mcServer, mcServer.getWorldServer(0), profile, new PlayerInteractionManager(mcServer.getWorldServer(0))); ++ Player player = entity.getBukkitEntity(); ++ //Spigot Start ++ PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginServer.networkManager.getRawAddress()).getAddress()); ++ //Spigot End ++ if (getBannedPlayers().isBanned(profile) && !getBannedPlayers().getEntry(profile).hasBanExpired()) ++ { ++ UserListBansEntry userlistbansentry = (UserListBansEntry)this.bannedPlayers.getEntry(profile); ++ String s1 = "You are banned from this server!\nReason: " + userlistbansentry.getBanReason(); ++ ++ if (userlistbansentry.getBanEndDate() != null) ++ { ++ s1 = s1 + "\nYour ban will be removed on " + DATE_FORMAT.format(userlistbansentry.getBanEndDate()); ++ } ++ ++ // return s1; ++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s1); ++ } ++ else if (!this.canJoin(profile)) ++ { ++ // return "You are not white-listed on this server!"; ++ event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, "You are not white-listed on this server!"); ++ } ++ else if (getBannedIPs().isBanned(socketaddress) && !getBannedIPs().getBanEntry(socketaddress).hasBanExpired()) ++ { ++ UserListIPBansEntry userlistipbansentry = this.bannedIPs.getBanEntry(socketaddress); ++ String s = "Your IP address is banned from this server!\nReason: " + userlistipbansentry.getBanReason(); ++ ++ if (userlistipbansentry.getBanEndDate() != null) ++ { ++ s = s + "\nYour ban will be removed on " + DATE_FORMAT.format(userlistipbansentry.getBanEndDate()); ++ } ++ ++ // return s; ++ if (!userlistipbansentry.hasBanExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s); ++ } ++ else ++ { ++ if (this.playerEntityList.size() >= this.maxPlayers && !this.bypassesPlayerLimit(profile)) { ++ event.disallow(PlayerLoginEvent.Result.KICK_FULL, "The server is full"); ++ } ++ } ++ ++ Bukkit.getPluginManager().callEvent(event); ++ if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { ++ loginServer.disconnect(new TextComponentTranslation(event.getKickMessage())); ++ return null; ++ } ++ return entity; ++ } ++ + public EntityPlayerMP createPlayerForUser(GameProfile profile) + { + UUID uuid = EntityPlayer.getUUID(profile); +@@ -504,26 +726,37 @@ + return new EntityPlayerMP(this.mcServer, this.mcServer.getWorld(0), profile, playerinteractionmanager); + } + ++ public EntityPlayerMP createPlayerForUser(GameProfile profile, EntityPlayerMP entityPlayerMP) ++ { ++ return entityPlayerMP; ++ } ++ + public EntityPlayerMP recreatePlayerEntity(EntityPlayerMP playerIn, int dimension, boolean conqueredEnd) + { ++ return this.moveToWorld(playerIn, dimension, conqueredEnd, null, true); ++ } ++ ++ public EntityPlayerMP moveToWorld(EntityPlayerMP playerIn, int dimension, boolean conqueredEnd, Location location, boolean avoidSuffocation) { ++ playerIn.dismountRidingEntity(); // CraftBukkit + World world = mcServer.getWorld(dimension); + if (world == null) + { + dimension = playerIn.getSpawnDimension(); + } +- else if (!world.provider.canRespawnHere()) ++ else if (location == null && !world.provider.canRespawnHere()) + { + dimension = world.provider.getRespawnDimension(playerIn); + } + if (mcServer.getWorld(dimension) == null) dimension = 0; + + playerIn.getServerWorld().getEntityTracker().removePlayerFromTrackers(playerIn); +- playerIn.getServerWorld().getEntityTracker().untrack(playerIn); ++ // playerIn.getServerWorld().getEntityTracker().untrack(playerIn); + playerIn.getServerWorld().getPlayerChunkMap().removePlayer(playerIn); + this.playerEntityList.remove(playerIn); + this.mcServer.getWorld(playerIn.dimension).removeEntityDangerously(playerIn); + BlockPos blockpos = playerIn.getBedLocation(dimension); +- boolean flag = playerIn.isSpawnForced(dimension); ++ boolean flag1 = playerIn.isSpawnForced(dimension); ++ /* CraftBukkit start + playerIn.dimension = dimension; + PlayerInteractionManager playerinteractionmanager; + +@@ -537,6 +770,11 @@ + } + + EntityPlayerMP entityplayermp = new EntityPlayerMP(this.mcServer, this.mcServer.getWorld(playerIn.dimension), playerIn.getGameProfile(), playerinteractionmanager); ++ */ ++ EntityPlayerMP entityplayermp = playerIn; ++ org.bukkit.World fromWorld = playerIn.getBukkitEntity().getWorld(); ++ playerIn.queuedEndExit = false; ++ + entityplayermp.connection = playerIn.connection; + entityplayermp.copyFrom(playerIn, conqueredEnd); + entityplayermp.dimension = dimension; +@@ -549,48 +787,178 @@ + entityplayermp.addTag(s); + } + +- WorldServer worldserver = this.mcServer.getWorld(playerIn.dimension); +- this.setPlayerGameTypeBasedOnOther(entityplayermp, playerIn, worldserver); ++ // WorldServer worldserver = this.mcServer.getWorld(playerIn.dimension); // CraftBukkit - handled later ++ // this.setPlayerGameTypeBasedOnOther(entityplayermp, playerIn, worldserver); ++ BlockPos blockposition1; ++ // CraftBukkit start - fire PlayerRespawnEvent ++ if (location == null) { ++ boolean isBedSpawn = false; ++ CraftWorld cworld = (CraftWorld) this.mcServer.server.getWorld(playerIn.spawnWorld); ++ if (cworld != null && blockpos != null) { ++ blockposition1 = EntityPlayer.getBedSpawnLocation(cworld.getHandle(), blockpos, flag1); ++ if (blockposition1 != null) { ++ isBedSpawn = true; ++ location = new Location(cworld, (double) ((float) blockposition1.getX() + 0.5F), (double) ((float) blockposition1.getY() + 0.1F), (double) ((float) blockposition1.getZ() + 0.5F)); ++ } else { ++ entityplayermp.setSpawnPoint(null, true); ++ entityplayermp.connection.sendPacket(new SPacketChangeGameState(0, 0.0F)); ++ } ++ } + +- if (blockpos != null) +- { +- BlockPos blockpos1 = EntityPlayer.getBedSpawnLocation(this.mcServer.getWorld(playerIn.dimension), blockpos, flag); +- +- if (blockpos1 != null) +- { +- entityplayermp.setLocationAndAngles((double)((float)blockpos1.getX() + 0.5F), (double)((float)blockpos1.getY() + 0.1F), (double)((float)blockpos1.getZ() + 0.5F), 0.0F, 0.0F); +- entityplayermp.setSpawnPoint(blockpos, flag); ++ if (location == null) { ++ cworld = (CraftWorld) this.mcServer.server.getWorlds().get(0); ++ blockpos = entityplayermp.getSpawnPoint(this.mcServer, cworld.getHandle()); ++ location = new Location(cworld, (double) ((float) blockpos.getX() + 0.5F), (double) ((float) blockpos.getY() + 0.1F), (double) ((float) blockpos.getZ() + 0.5F)); + } +- else +- { +- entityplayermp.connection.sendPacket(new SPacketChangeGameState(0, 0.0F)); ++ Player respawnPlayer = cserver.getPlayer(entityplayermp); ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn); ++ Bukkit.getPluginManager().callEvent(respawnEvent); ++ // Spigot Start ++ if (playerIn.connection.isDisconnected()) { ++ return playerIn; + } ++ // Spigot End ++ ++ location = respawnEvent.getRespawnLocation(); ++ playerIn.reset(); ++ } else { ++ location.setWorld(mcServer.getWorld(dimension).getWorld()); + } + ++ WorldServer worldserver = ((CraftWorld) location.getWorld()).getHandle(); ++ entityplayermp.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ + worldserver.getChunkProvider().provideChunk((int)entityplayermp.posX >> 4, (int)entityplayermp.posZ >> 4); + +- while (!worldserver.getCollisionBoxes(entityplayermp, entityplayermp.getEntityBoundingBox()).isEmpty() && entityplayermp.posY < 256.0D) ++ while (avoidSuffocation && !worldserver.getCollisionBoxes(entityplayermp, entityplayermp.getEntityBoundingBox()).isEmpty() && entityplayermp.posY < 256.0D) + { + entityplayermp.setPosition(entityplayermp.posX, entityplayermp.posY + 1.0D, entityplayermp.posZ); + } + +- entityplayermp.connection.sendPacket(new SPacketRespawn(entityplayermp.dimension, entityplayermp.world.getDifficulty(), entityplayermp.world.getWorldInfo().getTerrainType(), entityplayermp.interactionManager.getGameType())); ++ int actualDimension = worldserver.provider.getDimension(); ++ // Kettle - change dim for bukkit added dimensions ++ if (DimensionManager.isBukkitDimension(actualDimension)) ++ { ++ FMLEmbeddedChannel serverChannel = ForgeNetworkHandler.getServerChannel(); ++ serverChannel.attr(FMLOutboundHandler.FML_MESSAGETARGET).set(OutboundTarget.PLAYER); ++ serverChannel.attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(entityplayermp); ++ serverChannel.writeOutbound(new DimensionRegisterMessage(actualDimension, DimensionManager.getProviderType(actualDimension).name())); ++ } ++ // Kettle end ++ ++ // entityplayermp.connection.sendPacket(new SPacketRespawn(entityplayermp.dimension, entityplayermp.world.getDifficulty(), entityplayermp.world.getWorldInfo().getTerrainType(), entityplayermp.interactionManager.getGameType())); ++ entityplayermp.connection.sendPacket(new SPacketRespawn(actualDimension, worldserver.getDifficulty(), worldserver.getWorldInfo().getTerrainType(), entityplayermp.interactionManager.getGameType())); ++ entityplayermp.setWorld(worldserver); ++ entityplayermp.isDead = false; ++ entityplayermp.connection.teleport(new Location(worldserver.getWorld(), entityplayermp.posX, entityplayermp.posY, entityplayermp.posZ, entityplayermp.rotationYaw, entityplayermp.rotationPitch)); ++ entityplayermp.setSneaking(false); + BlockPos blockpos2 = worldserver.getSpawnPoint(); +- entityplayermp.connection.setPlayerLocation(entityplayermp.posX, entityplayermp.posY, entityplayermp.posZ, entityplayermp.rotationYaw, entityplayermp.rotationPitch); ++ // entityplayermp.connection.setPlayerLocation(entityplayermp.posX, entityplayermp.posY, entityplayermp.posZ, entityplayermp.rotationYaw, entityplayermp.rotationPitch); + entityplayermp.connection.sendPacket(new SPacketSpawnPosition(blockpos2)); + entityplayermp.connection.sendPacket(new SPacketSetExperience(entityplayermp.experience, entityplayermp.experienceTotal, entityplayermp.experienceLevel)); + this.updateTimeAndWeatherForPlayer(entityplayermp, worldserver); + this.updatePermissionLevel(entityplayermp); +- worldserver.getPlayerChunkMap().addPlayer(entityplayermp); +- worldserver.spawnEntity(entityplayermp); +- this.playerEntityList.add(entityplayermp); +- this.uuidToPlayerMap.put(entityplayermp.getUniqueID(), entityplayermp); +- entityplayermp.addSelfToInternalCraftingInventory(); ++ if (!playerIn.connection.isDisconnected()) { ++ worldserver.getPlayerChunkMap().addPlayer(entityplayermp); ++ worldserver.spawnEntity(entityplayermp); ++ this.playerEntityList.add(entityplayermp); ++ this.uuidToPlayerMap.put(entityplayermp.getUniqueID(), entityplayermp); ++ } ++ // entityplayermp.addSelfToInternalCraftingInventory(); + entityplayermp.setHealth(entityplayermp.getHealth()); ++ // Added from changeDimension ++ syncPlayerInventory(playerIn); // Update health, etc... ++ playerIn.sendPlayerAbilities(); ++ for (Object o1 : playerIn.getActivePotionEffects()) { ++ PotionEffect mobEffect = (PotionEffect) o1; ++ playerIn.connection.sendPacket(new SPacketEntityEffect(playerIn.getEntityId(), mobEffect)); ++ } ++ ++ // Fire advancement trigger ++ CriteriaTriggers.CHANGED_DIMENSION.trigger(playerIn, ((CraftWorld) fromWorld).getHandle().provider.getDimensionType(), worldserver.provider.getDimensionType()); ++ if (((CraftWorld) fromWorld).getHandle().provider.getDimensionType() == DimensionType.NETHER && worldserver.provider.getDimensionType() == DimensionType.OVERWORLD && playerIn.getEnteredNetherPosition() != null) { ++ CriteriaTriggers.NETHER_TRAVEL.trigger(playerIn, playerIn.getEnteredNetherPosition()); ++ } ++ ++ // Don't fire on respawn ++ if (fromWorld != location.getWorld()) { ++ PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(playerIn.getBukkitEntity(), fromWorld); ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ ++ // Save player file again if they were disconnected ++ if (playerIn.connection.isDisconnected()) { ++ this.writePlayerData(playerIn); ++ } ++ // CraftBukkit end + net.minecraftforge.fml.common.FMLCommonHandler.instance().firePlayerRespawnEvent(entityplayermp, conqueredEnd); + return entityplayermp; + } + ++ // CraftBukkit start - Replaced the standard handling of portals with a more customised method. ++ public void changeDimension(EntityPlayerMP entityplayer, int i, PlayerTeleportEvent.TeleportCause cause) { ++ WorldServer exitWorld = null; ++ if (entityplayer.dimension < CraftWorld.CUSTOM_DIMENSION_OFFSET) { // plugins must specify exit from custom Bukkit worlds ++ // only target existing worlds (compensate for allow-nether/allow-end as false) ++ for (WorldServer world : this.mcServer.worldServerList) { ++ if (world.dimension == i) { ++ exitWorld = world; ++ } ++ } ++ } ++ ++ Location enter = entityplayer.getBukkitEntity().getLocation(); ++ Location exit = null; ++ boolean useTravelAgent = false; // don't use agent for custom worlds or return from THE_END ++ if (exitWorld != null) { ++ if ((cause == PlayerTeleportEvent.TeleportCause.END_PORTAL) && (i == 0)) { ++ // THE_END -> NORMAL; use bed if available, otherwise default spawn ++ exit = ((org.bukkit.craftbukkit.entity.CraftPlayer) entityplayer.getBukkitEntity()).getBedSpawnLocation(); ++ if (exit == null || ((CraftWorld) exit.getWorld()).getHandle().dimension != 0) { ++ BlockPos randomSpawn = entityplayer.getSpawnPoint(mcServer, exitWorld); ++ exit = new Location(exitWorld.getWorld(), randomSpawn.getX(), randomSpawn.getY(), randomSpawn.getZ()); ++ } else { ++ exit = exit.add(0.5F, 0.1F, 0.5F); // SPIGOT-3879 ++ } ++ } else { ++ exit = this.calculateTarget(enter, exitWorld); ++ if (cause != cause.MOD) // don't use travel agent for custom dimensions ++ { ++ useTravelAgent = true; ++ } ++ } ++ } ++ ++ TravelAgent agent = exit != null ? (TravelAgent) ((CraftWorld) exit.getWorld()).getHandle().getDefaultTeleporter() : org.bukkit.craftbukkit.CraftTravelAgent.DEFAULT; // return arbitrary TA to compensate for implementation dependent plugins ++ PlayerPortalEvent event = new PlayerPortalEvent(entityplayer.getBukkitEntity(), enter, exit, agent, cause); ++ event.useTravelAgent(useTravelAgent); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null) { ++ return; ++ } ++ ++ exit = event.useTravelAgent() && cause != cause.MOD ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo(); ++ if (exit == null) { ++ return; ++ } ++ exitWorld = ((CraftWorld) exit.getWorld()).getHandle(); ++ ++ PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(entityplayer.getBukkitEntity(), enter, exit, cause); ++ Bukkit.getServer().getPluginManager().callEvent(tpEvent); ++ if (tpEvent.isCancelled() || tpEvent.getTo() == null) { ++ return; ++ } ++ ++ Vector velocity = entityplayer.getBukkitEntity().getVelocity(); ++ exitWorld.getDefaultTeleporter().adjustExit(entityplayer, exit, velocity); ++ ++ entityplayer.invulnerableDimensionChange = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds ++ this.moveToWorld(entityplayer, exitWorld.dimension, true, exit, true); // SPIGOT-3864 ++ if (entityplayer.motionX != velocity.getX() || entityplayer.motionY != velocity.getY() || entityplayer.motionZ != velocity.getZ()) { ++ entityplayer.getBukkitEntity().setVelocity(velocity); ++ } ++ } ++ + public void updatePermissionLevel(EntityPlayerMP player) + { + GameProfile gameprofile = player.getGameProfile(); +@@ -642,7 +1010,9 @@ + + public void transferEntityToWorld(Entity entityIn, int lastDimension, WorldServer oldWorldIn, WorldServer toWorldIn) + { +- transferEntityToWorld(entityIn, lastDimension, oldWorldIn, toWorldIn, toWorldIn.getDefaultTeleporter()); ++ // transferEntityToWorld(entityIn, lastDimension, oldWorldIn, toWorldIn, toWorldIn.getDefaultTeleporter()); ++ Location exit = this.calculateTarget(entityIn.getBukkitEntity().getLocation(), toWorldIn); ++ this.repositionEntity(entityIn, exit, true); + } + + // TODO: Remove (1.13) +@@ -729,11 +1099,158 @@ + entityIn.setWorld(toWorldIn); + } + ++ // Copy of original transferEntityToWorld(Entity, int, WorldServer, WorldServer) method with only location calculation logic ++ public Location calculateTarget(Location enter, World target) { ++ WorldServer worldserver = ((CraftWorld) enter.getWorld()).getHandle(); ++ WorldServer worldserver1 = ((CraftWorld) target.getWorld()).getHandle(); ++ int i = worldserver.dimension; ++ ++ double y = enter.getY(); ++ float yaw = enter.getYaw(); ++ float pitch = enter.getPitch(); ++ double d0 = enter.getX(); ++ double d1 = enter.getZ(); ++ double d2 = 8.0D; ++ ++ worldserver.profiler.startSection("moving"); ++ if (worldserver1.dimension == -1) { ++ d0 = MathHelper.clamp(d0 / d2, worldserver1.getWorldBorder().minX()+ 16.0D, worldserver1.getWorldBorder().maxX() - 16.0D); ++ d1 = MathHelper.clamp(d1 / d2, worldserver1.getWorldBorder().minZ() + 16.0D, worldserver1.getWorldBorder().maxZ() - 16.0D); ++ } else if (worldserver1.dimension == 0) { ++ d0 = MathHelper.clamp(d0 * d2, worldserver1.getWorldBorder().minX() + 16.0D, worldserver1.getWorldBorder().maxX() - 16.0D); ++ d1 = MathHelper.clamp(d1 * d2, worldserver1.getWorldBorder().minZ() + 16.0D, worldserver1.getWorldBorder().maxZ() - 16.0D); ++ } else { ++ BlockPos blockposition; ++ ++ if (i == 1) { ++ // use default NORMAL world spawn instead of target ++ worldserver1 = this.mcServer.worlds[0]; ++ blockposition = worldserver1.getSpawnPoint(); ++ } else { ++ blockposition = worldserver1.getSpawnCoordinate(); ++ } ++ ++ if (blockposition != null) { ++ d0 = (double) blockposition.getX(); ++ y = (double) blockposition.getY(); ++ d1 = (double) blockposition.getZ(); ++ } ++ /* ++ entity.setPositionRotation(d0, entity.locY, d1, 90.0F, 0.0F); ++ if (entity.isAlive()) { ++ worldserver.entityJoinedWorld(entity, false); ++ } ++ */ ++ } ++ ++ worldserver.profiler.endSection(); ++ if (i != 1) { ++ worldserver.profiler.startSection("placing"); ++ d0 = (double) MathHelper.clamp((int) d0, -29999872, 29999872); ++ d1 = (double) MathHelper.clamp((int) d1, -29999872, 29999872); ++ worldserver.profiler.endSection(); ++ } ++ ++ return new Location(worldserver1.getWorld(), d0, y, d1, yaw, pitch); ++ } ++ ++ // copy of original transferEntityToWorld(Entity, int, WorldServer, WorldServer) method with only entity repositioning logic ++ public void repositionEntity(Entity entity, Location exit, boolean portal) { ++ WorldServer worldserver = (WorldServer) entity.world; ++ WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle(); ++ int i = worldserver.dimension; ++ ++ /* ++ double d0 = entity.locX; ++ double d1 = entity.locZ; ++ double d2 = 8.0D; ++ float f = entity.yaw; ++ */ ++ ++ worldserver.profiler.startSection("moving"); ++ entity.setLocationAndAngles(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch()); ++ if (entity.isEntityAlive()) { ++ worldserver.updateEntityWithOptionalForce(entity, false); ++ } ++ /* ++ if (entity.dimension == -1) { ++ d0 = MathHelper.a(d0 / 8.0D, worldserver1.getWorldBorder().b() + 16.0D, worldserver1.getWorldBorder().d() - 16.0D); ++ d1 = MathHelper.a(d1 / 8.0D, worldserver1.getWorldBorder().c() + 16.0D, worldserver1.getWorldBorder().e() - 16.0D); ++ entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch); ++ if (entity.isAlive()) { ++ worldserver.entityJoinedWorld(entity, false); ++ } ++ } else if (entity.dimension == 0) { ++ d0 = MathHelper.a(d0 * 8.0D, worldserver1.getWorldBorder().b() + 16.0D, worldserver1.getWorldBorder().d() - 16.0D); ++ d1 = MathHelper.a(d1 * 8.0D, worldserver1.getWorldBorder().c() + 16.0D, worldserver1.getWorldBorder().e() - 16.0D); ++ entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch); ++ if (entity.isAlive()) { ++ worldserver.entityJoinedWorld(entity, false); ++ } ++ } else { ++ BlockPosition blockposition; ++ ++ if (i == 1) { ++ // use default NORMAL world spawn instead of target ++ worldserver1 = this.server.worlds.get(0); ++ blockposition = worldserver1.getSpawn(); ++ } else { ++ blockposition = worldserver1.getDimensionSpawn(); ++ } ++ ++ d0 = (double) blockposition.getX(); ++ entity.locY = (double) blockposition.getY(); ++ d1 = (double) blockposition.getZ(); ++ entity.setPositionRotation(d0, entity.locY, d1, 90.0F, 0.0F); ++ if (entity.isAlive()) { ++ worldserver.entityJoinedWorld(entity, false); ++ } ++ } ++ */ ++ ++ worldserver.profiler.endSection(); ++ if (i != 1) { ++ worldserver.profiler.startSection("placing"); ++ /* ++ d0 = (double) MathHelper.clamp((int) d0, -29999872, 29999872); ++ d1 = (double) MathHelper.clamp((int) d1, -29999872, 29999872); ++ */ ++ if (entity.isEntityAlive()) { ++ // entity.setPositionRotation(d0, entity.locY, d1, entity.yaw, entity.pitch); ++ // worldserver1.getTravelAgent().a(entity, f); ++ if (portal) { ++ Vector velocity = entity.getBukkitEntity().getVelocity(); ++ worldserver1.getDefaultTeleporter().adjustExit(entity, exit, velocity); ++ entity.setLocationAndAngles(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch()); ++ if (entity.motionX != velocity.getX() || entity.motionY != velocity.getY() || entity.motionZ != velocity.getZ()) { ++ entity.getBukkitEntity().setVelocity(velocity); ++ } ++ } ++ // worldserver1.addEntity(entity); ++ worldserver1.updateEntityWithOptionalForce(entity, false); ++ } ++ ++ worldserver.profiler.endSection(); ++ } ++ ++ entity.setWorld(worldserver1); ++ } ++ + public void onTick() + { + if (++this.playerPingIndex > 600) + { +- this.sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_LATENCY, this.playerEntityList)); ++ // this.sendPacketToAllPlayers(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_LATENCY, this.playerEntityList)); ++ for (int i = 0; i < this.playerEntityList.size(); ++i) { ++ final EntityPlayerMP target = this.playerEntityList.get(i); ++ ++ target.connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_LATENCY, Iterables.filter(this.playerEntityList, new Predicate() { ++ @Override ++ public boolean apply(EntityPlayerMP input) { ++ return target.getBukkitEntity().canSee(input.getBukkitEntity()); ++ } ++ }))); ++ } + this.playerPingIndex = 0; + } + } +@@ -746,6 +1263,24 @@ + } + } + ++ // CraftBukkit start - add a world/entity limited version ++ public void sendAll(Packet packet, EntityPlayer entityhuman) { ++ for (int i = 0; i < this.playerEntityList.size(); ++i) { ++ EntityPlayerMP entityplayer = this.playerEntityList.get(i); ++ if (entityhuman != null && entityhuman instanceof EntityPlayerMP && !entityplayer.getBukkitEntity().canSee(((EntityPlayerMP) entityhuman).getBukkitEntity())) { ++ continue; ++ } ++ ((EntityPlayerMP) this.playerEntityList.get(i)).connection.sendPacket(packet); ++ } ++ } ++ ++ public void sendAll(Packet packet, World world) { ++ for (int i = 0; i < world.playerEntities.size(); ++i) { ++ ((EntityPlayerMP) world.playerEntities.get(i)).connection.sendPacket(packet); ++ } ++ } ++ // CraftBukkit end ++ + public void sendPacketToAllPlayersInDimension(Packet packetIn, int dimension) + { + for (int i = 0; i < this.playerEntityList.size(); ++i) +@@ -861,12 +1396,20 @@ + int i = this.mcServer.getOpPermissionLevel(); + this.ops.addEntry(new UserListOpsEntry(profile, this.mcServer.getOpPermissionLevel(), this.ops.bypassesPlayerLimit(profile))); + this.sendPlayerPermissionLevel(this.getPlayerByUUID(profile.getId()), i); ++ Player player = mcServer.server.getPlayer(profile.getId()); ++ if (player != null) { ++ player.recalculatePermissions(); ++ } + } + + public void removeOp(GameProfile profile) + { + this.ops.removeEntry(profile); + this.sendPlayerPermissionLevel(this.getPlayerByUUID(profile.getId()), 0); ++ Player player = mcServer.server.getPlayer(profile.getId()); ++ if (player != null) { ++ player.recalculatePermissions(); ++ } + } + + private void sendPlayerPermissionLevel(EntityPlayerMP player, int permLevel) +@@ -918,11 +1461,33 @@ + + public void sendToAllNearExcept(@Nullable EntityPlayer except, double x, double y, double z, double radius, int dimension, Packet packetIn) + { +- for (int i = 0; i < this.playerEntityList.size(); ++i) +- { +- EntityPlayerMP entityplayermp = this.playerEntityList.get(i); ++ // Paper start - Use world list instead of server list where preferable ++ sendToAllNearExcept(except, x, y, z, radius, dimension, null, packetIn); // Retained for compatibility ++ } + +- if (entityplayermp != except && entityplayermp.dimension == dimension) ++ public void sendToAllNearExcept(@Nullable EntityPlayer except, double d0, double d1, double d2, double d3, WorldServer world, Packet packet) { ++ sendToAllNearExcept(except, d0, d1, d2, d3, world.dimension, world, packet); ++ } ++ ++ public void sendToAllNearExcept(@Nullable EntityPlayer except, double x, double y, double z, double radius, int dimension, @Nullable WorldServer world, Packet packetIn) { ++ if (world == null && except != null && except.world instanceof WorldServer) { ++ world = (WorldServer) except.world; ++ } ++ ++ List players1 = world == null ? playerEntityList : world.playerEntities; ++ for (int j = 0; j < players1.size(); ++j) { ++ EntityPlayer entity = players1.get(j); ++ if (!(entity instanceof EntityPlayerMP)) continue; ++ EntityPlayerMP entityplayermp = (EntityPlayerMP) players1.get(j); ++ // Paper end ++ ++ // CraftBukkit start - Test if player receiving packet can see the source of the packet ++ if (except != null && except instanceof EntityPlayerMP && !entityplayermp.getBukkitEntity().canSee(((EntityPlayerMP) except).getBukkitEntity())) { ++ continue; ++ } ++ // CraftBukkit end ++ ++ if (entityplayermp != except && (world != null || entityplayermp.dimension == dimension)) // Paper + { + double d0 = x - entityplayermp.posX; + double d1 = y - entityplayermp.posY; +@@ -938,11 +1503,23 @@ + + public void saveAllPlayerData() + { +- for (int i = 0; i < this.playerEntityList.size(); ++i) +- { +- this.writePlayerData(this.playerEntityList.get(i)); ++ saveAllPlayerData(null); ++ } ++ ++ public void saveAllPlayerData(Integer interval) { ++ MCUtil.ensureMain("Save Players", () -> { // Paper - ensure main ++ long now = MinecraftServer.currentTick; ++ int numSaved = 0; // Paper ++ for (int i = 0; i < this.playerEntityList.size(); ++i) { ++ EntityPlayerMP entityplayer = this.playerEntityList.get(i); ++ if (interval == null || now - entityplayer.lastSave >= interval) { ++ this.writePlayerData(entityplayer); ++ if (interval != null && ++numSaved <= 10) { break; } // Paper + } + } ++ return null; }); // Paper - ensure main ++ } ++ // Paper end + + public void addWhitelistedPlayer(GameProfile profile) + { +@@ -988,16 +1565,21 @@ + + if (worldIn.isRaining()) + { +- playerIn.connection.sendPacket(new SPacketChangeGameState(1, 0.0F)); +- playerIn.connection.sendPacket(new SPacketChangeGameState(7, worldIn.getRainStrength(1.0F))); +- playerIn.connection.sendPacket(new SPacketChangeGameState(8, worldIn.getThunderStrength(1.0F))); ++ // CraftBukkit start - handle player weather ++ // playerIn.connection.sendPacket(new SPacketChangeGameState(1, 0.0F)); ++ // playerIn.connection.sendPacket(new SPacketChangeGameState(7, worldIn.getRainStrength(1.0F))); ++ // playerIn.connection.sendPacket(new SPacketChangeGameState(8, worldIn.getThunderStrength(1.0F))); ++ playerIn.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false); ++ playerIn.updateWeather(-worldIn.rainingStrength, worldIn.rainingStrength, -worldIn.thunderingStrength, worldIn.thunderingStrength); ++ // CraftBukkit end + } + } + + public void syncPlayerInventory(EntityPlayerMP playerIn) + { + playerIn.sendContainerToPlayer(playerIn.inventoryContainer); +- playerIn.setPlayerHealthUpdated(); ++ // playerIn.setPlayerHealthUpdated(); ++ playerIn.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange + playerIn.connection.sendPacket(new SPacketHeldItemChange(playerIn.inventory.currentItem)); + } + +@@ -1079,17 +1661,26 @@ + + public void removeAllPlayers() + { +- for (int i = 0; i < this.playerEntityList.size(); ++i) +- { +- (this.playerEntityList.get(i)).connection.disconnect(new TextComponentTranslation("multiplayer.disconnect.server_shutdown", new Object[0])); ++ // CraftBukkit start - disconnect safely ++ for (EntityPlayerMP player : this.playerEntityList) { ++ player.connection.disconnect(this.mcServer.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message + } + } + ++ public void sendMessage(ITextComponent[] iChatBaseComponents) { ++ for (ITextComponent component : iChatBaseComponents) { ++ sendMessage(component, true); ++ } ++ } ++ + public void sendMessage(ITextComponent component, boolean isSystem) + { + this.mcServer.sendMessage(component); + ChatType chattype = isSystem ? ChatType.SYSTEM : ChatType.CHAT; +- this.sendPacketToAllPlayers(new SPacketChat(component, chattype)); ++ // this.sendPacketToAllPlayers(new SPacketChat(component, chattype)); ++ // CraftBukkit start - we run this through our processor first so we can get web links etc ++ this.sendPacketToAllPlayers(new SPacketChat(CraftChatMessage.fixComponent(component), chattype)); ++ // CraftBukkit end + } + + public void sendMessage(ITextComponent component) +@@ -1097,10 +1688,19 @@ + this.sendMessage(component, true); + } + +- public StatisticsManagerServer getPlayerStatsFile(EntityPlayer playerIn) ++ @Nullable ++ public StatisticsManagerServer getPlayerStatsFile(EntityPlayer playerIn) { ++ if (playerIn instanceof EntityPlayerMP) { ++ return this.getPlayerStatsFile((EntityPlayerMP) playerIn); ++ } else { ++ return null; ++ } ++ } ++ ++ public StatisticsManagerServer getPlayerStatsFile(EntityPlayerMP playerIn) + { + UUID uuid = playerIn.getUniqueID(); +- StatisticsManagerServer statisticsmanagerserver = uuid == null ? null : (StatisticsManagerServer)this.playerStatFiles.get(uuid); ++ StatisticsManagerServer statisticsmanagerserver = uuid == null ? null : (StatisticsManagerServer)playerIn.getStatFile(); + + if (statisticsmanagerserver == null) + { +@@ -1119,7 +1719,7 @@ + + statisticsmanagerserver = new StatisticsManagerServer(this.mcServer, file2); + statisticsmanagerserver.readStatFile(); +- this.playerStatFiles.put(uuid, statisticsmanagerserver); ++ // this.playerStatFiles.put(uuid, statisticsmanagerserver); + } + + return statisticsmanagerserver; +@@ -1128,14 +1728,14 @@ + public PlayerAdvancements getPlayerAdvancements(EntityPlayerMP p_192054_1_) + { + UUID uuid = p_192054_1_.getUniqueID(); +- PlayerAdvancements playeradvancements = this.advancements.get(uuid); ++ PlayerAdvancements playeradvancements = p_192054_1_.getAdvancements(); + + if (playeradvancements == null) + { + File file1 = new File(this.mcServer.getWorld(0).getSaveHandler().getWorldDirectory(), "advancements"); + File file2 = new File(file1, uuid + ".json"); + playeradvancements = new PlayerAdvancements(this.mcServer, file2, p_192054_1_); +- this.advancements.put(uuid, playeradvancements); ++ // this.advancements.put(uuid, playeradvancements); + } + + playeradvancements.setPlayer(p_192054_1_); +@@ -1148,7 +1748,7 @@ + + if (this.mcServer.worlds != null) + { +- for (WorldServer worldserver : this.mcServer.worlds) ++ for (WorldServer worldserver : this.mcServer.worldServerList) + { + if (worldserver != null) + { +@@ -1176,9 +1776,10 @@ + + public void reloadResources() + { +- for (PlayerAdvancements playeradvancements : this.advancements.values()) ++ for (EntityPlayerMP entityPlayerMP : this.playerEntityList) + { +- playeradvancements.reload(); ++ entityPlayerMP.getAdvancements().reload(); ++ entityPlayerMP.getAdvancements().flushDirty(entityPlayerMP); // CraftBukkit - trigger immediate flush of advancements + } + } + diff --git a/patches/net/minecraft/server/management/PlayerProfileCache.java.patch b/patches/net/minecraft/server/management/PlayerProfileCache.java.patch new file mode 100644 index 00000000..6fabdec2 --- /dev/null +++ b/patches/net/minecraft/server/management/PlayerProfileCache.java.patch @@ -0,0 +1,57 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PlayerProfileCache.java ++++ ../src-work/minecraft/net/minecraft/server/management/PlayerProfileCache.java +@@ -45,9 +45,9 @@ + { + public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + private static boolean onlineMode; +- private final Map usernameToProfileEntryMap = Maps.newHashMap(); +- private final Map uuidToProfileEntryMap = Maps.newHashMap(); +- private final Deque gameProfiles = Lists.newLinkedList(); ++ private final Map usernameToProfileEntryMap = Maps.newHashMap(); ++ private final Map uuidToProfileEntryMap = Maps.newHashMap(); ++ private final Deque gameProfiles = new java.util.concurrent.LinkedBlockingDeque<>(); + private final GameProfileRepository profileRepo; + protected final Gson gson; + private final File usercacheFile; +@@ -93,8 +93,7 @@ + }; + profileRepoIn.findProfilesByNames(new String[] {name}, Agent.MINECRAFT, profilelookupcallback); + +- if (!isOnlineMode() && agameprofile[0] == null) +- { ++ if (!isOnlineMode() && agameprofile[0] == null && !org.apache.commons.lang3.StringUtils.isBlank(name)) { // Paper - Don't lookup a profile with a blank name + UUID uuid = EntityPlayer.getUUID(new GameProfile((UUID)null, name)); + GameProfile gameprofile = new GameProfile(uuid, name); + profilelookupcallback.onProfileLookupSucceeded(gameprofile); +@@ -143,7 +142,7 @@ + this.usernameToProfileEntryMap.put(gameProfile.getName().toLowerCase(Locale.ROOT), playerprofilecache$profileentry); + this.uuidToProfileEntryMap.put(uuid, playerprofilecache$profileentry); + this.gameProfiles.addFirst(gameProfile); +- this.save(); ++ if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(); // Spigot - skip saving if disabled + } + + @Nullable +@@ -177,7 +176,7 @@ + } + } + +- this.save(); ++ if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(); // Spigot - skip saving if disabled + return playerprofilecache$profileentry == null ? null : playerprofilecache$profileentry.getGameProfile(); + } + +@@ -187,6 +186,13 @@ + return (String[])list.toArray(new String[list.size()]); + } + ++ // Paper start ++ @Nullable public GameProfile getProfileIfCached(String name) { ++ ProfileEntry entry = this.usernameToProfileEntryMap.get(name.toLowerCase(Locale.ROOT)); ++ return entry == null ? null : entry.gameProfile; ++ } ++ // Paper end ++ + @Nullable + public GameProfile getProfileByUUID(UUID uuid) + { diff --git a/patches/net/minecraft/server/management/PreYggdrasilConverter.java.patch b/patches/net/minecraft/server/management/PreYggdrasilConverter.java.patch new file mode 100644 index 00000000..d3c55e37 --- /dev/null +++ b/patches/net/minecraft/server/management/PreYggdrasilConverter.java.patch @@ -0,0 +1,238 @@ +--- ../src-base/minecraft/net/minecraft/server/management/PreYggdrasilConverter.java ++++ ../src-work/minecraft/net/minecraft/server/management/PreYggdrasilConverter.java +@@ -10,7 +10,6 @@ + import com.mojang.authlib.ProfileLookupCallback; + import com.mojang.authlib.yggdrasil.ProfileNotFoundException; + import java.io.File; +-import java.io.FileNotFoundException; + import java.io.IOException; + import java.nio.charset.StandardCharsets; + import java.text.ParseException; +@@ -22,6 +21,8 @@ + import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.nbt.CompressedStreamTools; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.server.dedicated.PropertyManager; +@@ -49,7 +50,7 @@ + } + }), String.class); + +- if (server.isServerInOnlineMode()) ++ if (server.isServerInOnlineMode() || org.spigotmc.SpigotConfig.bungee) // Spigot: bungee = online mode, for now. + { + server.getGameProfileRepository().findProfilesByNames(astring, Agent.MINECRAFT, callback); + } +@@ -135,9 +136,10 @@ + { + userlistbans.readSavedFile(); + } +- catch (FileNotFoundException filenotfoundexception) ++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacktrace ++ catch (IOException filenotfoundexception) + { +- LOGGER.warn("Could not load existing file {}", userlistbans.getSaveFile().getName(), filenotfoundexception); ++ LOGGER.warn("Could not load existing file {}", userlistbans.getSaveFile().getName()); + } + } + +@@ -155,7 +157,7 @@ + if (astring == null) + { + PreYggdrasilConverter.LOGGER.warn("Could not convert user banlist entry for {}", (Object)p_onProfileLookupSucceeded_1_.getName()); +- throw new PreYggdrasilConverter.ConversionError("Profile not in the conversionlist"); ++ throw new ConversionError("Profile not in the conversionlist"); + } + else + { +@@ -172,7 +174,7 @@ + + if (!(p_onProfileLookupFailed_2_ instanceof ProfileNotFoundException)) + { +- throw new PreYggdrasilConverter.ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); ++ throw new ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); + } + } + }; +@@ -186,7 +188,7 @@ + LOGGER.warn("Could not read old user banlist to convert it!", (Throwable)ioexception); + return false; + } +- catch (PreYggdrasilConverter.ConversionError preyggdrasilconverter$conversionerror) ++ catch (ConversionError preyggdrasilconverter$conversionerror) + { + LOGGER.error("Conversion failed, please try again later", (Throwable)preyggdrasilconverter$conversionerror); + return false; +@@ -211,9 +213,10 @@ + { + userlistipbans.readSavedFile(); + } +- catch (FileNotFoundException filenotfoundexception) ++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacktrace ++ catch (IOException filenotfoundexception) + { +- LOGGER.warn("Could not load existing file {}", userlistipbans.getSaveFile().getName(), filenotfoundexception); ++ LOGGER.warn("Could not load existing file {}", userlistipbans.getSaveFile().getName()); + } + } + +@@ -261,9 +264,10 @@ + { + userlistops.readSavedFile(); + } +- catch (FileNotFoundException filenotfoundexception) ++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacktrace ++ catch (IOException filenotfoundexception) + { +- LOGGER.warn("Could not load existing file {}", userlistops.getSaveFile().getName(), filenotfoundexception); ++ LOGGER.warn("Could not load existing file {}", userlistops.getSaveFile().getName()); + } + } + +@@ -283,7 +287,7 @@ + + if (!(p_onProfileLookupFailed_2_ instanceof ProfileNotFoundException)) + { +- throw new PreYggdrasilConverter.ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); ++ throw new ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); + } + } + }; +@@ -297,7 +301,7 @@ + LOGGER.warn("Could not read old oplist to convert it!", (Throwable)ioexception); + return false; + } +- catch (PreYggdrasilConverter.ConversionError preyggdrasilconverter$conversionerror) ++ catch (ConversionError preyggdrasilconverter$conversionerror) + { + LOGGER.error("Conversion failed, please try again later", (Throwable)preyggdrasilconverter$conversionerror); + return false; +@@ -322,9 +326,10 @@ + { + userlistwhitelist.readSavedFile(); + } +- catch (FileNotFoundException filenotfoundexception) ++ // CraftBukkit start - FileNotFoundException -> IOException, don't print stacktrace ++ catch (IOException filenotfoundexception) + { +- LOGGER.warn("Could not load existing file {}", userlistwhitelist.getSaveFile().getName(), filenotfoundexception); ++ LOGGER.warn("Could not load existing file {}", userlistwhitelist.getSaveFile().getName()); + } + } + +@@ -344,7 +349,7 @@ + + if (!(p_onProfileLookupFailed_2_ instanceof ProfileNotFoundException)) + { +- throw new PreYggdrasilConverter.ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); ++ throw new ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); + } + } + }; +@@ -358,7 +363,7 @@ + LOGGER.warn("Could not read old whitelist to convert it!", (Throwable)ioexception); + return false; + } +- catch (PreYggdrasilConverter.ConversionError preyggdrasilconverter$conversionerror) ++ catch (ConversionError preyggdrasilconverter$conversionerror) + { + LOGGER.error("Conversion failed, please try again later", (Throwable)preyggdrasilconverter$conversionerror); + return false; +@@ -409,7 +414,7 @@ + + if (uuid == null) + { +- throw new PreYggdrasilConverter.ConversionError("Missing UUID for user profile " + p_onProfileLookupSucceeded_1_.getName()); ++ throw new ConversionError("Missing UUID for user profile " + p_onProfileLookupSucceeded_1_.getName()); + } + else + { +@@ -427,18 +432,41 @@ + } + else + { +- throw new PreYggdrasilConverter.ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); ++ throw new ConversionError("Could not request user " + p_onProfileLookupFailed_1_.getName() + " from backend systems", p_onProfileLookupFailed_2_); + } + } + private void renamePlayerFile(File p_152743_1_, String p_152743_2_, String p_152743_3_) + { + File file5 = new File(file1, p_152743_2_ + ".dat"); + File file6 = new File(p_152743_1_, p_152743_3_ + ".dat"); ++ // CraftBukkit start - Use old file name to seed lastKnownName ++ NBTTagCompound root = null; ++ ++ try { ++ root = CompressedStreamTools.readCompressed(new java.io.FileInputStream(file1)); ++ } catch (Exception exception) { ++ exception.printStackTrace(); ++ } ++ ++ if (root != null) { ++ if (!root.hasKey("bukkit")) { ++ root.setTag("bukkit", new NBTTagCompound()); ++ } ++ NBTTagCompound data = root.getCompoundTag("bukkit"); ++ data.setString("lastKnownName", p_152743_2_); ++ ++ try { ++ CompressedStreamTools.writeCompressed(root, new java.io.FileOutputStream(file2)); ++ } catch (Exception exception) { ++ exception.printStackTrace(); ++ } ++ } ++ // CraftBukkit end + PreYggdrasilConverter.mkdir(p_152743_1_); + + if (!file5.renameTo(file6)) + { +- throw new PreYggdrasilConverter.ConversionError("Could not convert file for " + p_152743_2_); ++ throw new ConversionError("Could not convert file for " + p_152743_2_); + } + } + private String getFileNameForProfile(GameProfile p_152744_1_) +@@ -456,7 +484,7 @@ + + if (s2 == null) + { +- throw new PreYggdrasilConverter.ConversionError("Could not find the filename for " + p_152744_1_.getName() + " anymore"); ++ throw new ConversionError("Could not find the filename for " + p_152744_1_.getName() + " anymore"); + } + else + { +@@ -467,7 +495,7 @@ + lookupNames(server, Lists.newArrayList(astring), profilelookupcallback); + return true; + } +- catch (PreYggdrasilConverter.ConversionError preyggdrasilconverter$conversionerror) ++ catch (ConversionError preyggdrasilconverter$conversionerror) + { + LOGGER.error("Conversion failed, please try again later", (Throwable)preyggdrasilconverter$conversionerror); + return false; +@@ -486,12 +514,12 @@ + { + if (!dir.isDirectory()) + { +- throw new PreYggdrasilConverter.ConversionError("Can't create directory " + dir.getName() + " in world save directory."); ++ throw new ConversionError("Can't create directory " + dir.getName() + " in world save directory."); + } + } + else if (!dir.mkdirs()) + { +- throw new PreYggdrasilConverter.ConversionError("Can't create directory " + dir.getName() + " in world save directory."); ++ throw new ConversionError("Can't create directory " + dir.getName() + " in world save directory."); + } + } + +@@ -589,7 +617,7 @@ + private static File getPlayersDirectory(PropertyManager properties) + { + String s = properties.getStringProperty("level-name", "world"); +- File file1 = new File(s); ++ File file1 = new File(MinecraftServer.getServerCB().server.getWorldContainer(), s); // CraftBukkit - Respect container setting + return new File(file1, "players"); + } + diff --git a/patches/net/minecraft/server/management/UserList.java.patch b/patches/net/minecraft/server/management/UserList.java.patch new file mode 100644 index 00000000..864a5460 --- /dev/null +++ b/patches/net/minecraft/server/management/UserList.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/server/management/UserList.java ++++ ../src-work/minecraft/net/minecraft/server/management/UserList.java +@@ -156,6 +156,10 @@ + return this.values; + } + ++ public Collection getValuesCB() { ++ return this.values.values(); ++ } ++ + public void writeChanges() throws IOException + { + Collection collection = this.values.values(); diff --git a/patches/net/minecraft/server/management/UserListBansEntry.java.patch b/patches/net/minecraft/server/management/UserListBansEntry.java.patch new file mode 100644 index 00000000..c01d56c9 --- /dev/null +++ b/patches/net/minecraft/server/management/UserListBansEntry.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraft/server/management/UserListBansEntry.java ++++ ../src-work/minecraft/net/minecraft/server/management/UserListBansEntry.java +@@ -14,7 +14,7 @@ + + public UserListBansEntry(GameProfile profile, Date startDate, String banner, Date endDate, String banReason) + { +- super(profile, endDate, banner, endDate, banReason); ++ super(profile, startDate, banner, endDate, banReason); + } + + public UserListBansEntry(JsonObject json) +@@ -34,10 +34,13 @@ + + private static GameProfile toGameProfile(JsonObject json) + { +- if (json.has("uuid") && json.has("name")) ++ // Spigot start ++ // this whole method has to be reworked to account for the fact Bukkit only accepts UUID bans and gives no way for usernames to be stored! ++ UUID uuid = null; ++ String name = null; ++ if (json.has("uuid")) + { + String s = json.get("uuid").getAsString(); +- UUID uuid; + + try + { +@@ -45,14 +48,20 @@ + } + catch (Throwable var4) + { +- return null; + } +- +- return new GameProfile(uuid, json.get("name").getAsString()); ++ } ++ if (json.has("name")) ++ { ++ name = json.get("name").getAsString(); + } ++ if (uuid != null || name != null) ++ { ++ return new GameProfile(uuid, name); ++ } + else + { + return null; + } ++ // Spigot End + } + } diff --git a/patches/net/minecraft/server/management/UserListEntry.java.patch b/patches/net/minecraft/server/management/UserListEntry.java.patch new file mode 100644 index 00000000..76f33dbb --- /dev/null +++ b/patches/net/minecraft/server/management/UserListEntry.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/server/management/UserListEntry.java ++++ ../src-work/minecraft/net/minecraft/server/management/UserListEntry.java +@@ -16,7 +16,7 @@ + this.value = valueIn; + } + +- T getValue() ++ public T getValue() + { + return this.value; + } diff --git a/patches/net/minecraft/server/management/UserListEntryBan.java.patch b/patches/net/minecraft/server/management/UserListEntryBan.java.patch new file mode 100644 index 00000000..3b7d47a5 --- /dev/null +++ b/patches/net/minecraft/server/management/UserListEntryBan.java.patch @@ -0,0 +1,40 @@ +--- ../src-base/minecraft/net/minecraft/server/management/UserListEntryBan.java ++++ ../src-work/minecraft/net/minecraft/server/management/UserListEntryBan.java +@@ -24,7 +24,7 @@ + + protected UserListEntryBan(T valueIn, JsonObject json) + { +- super(valueIn, json); ++ super(checkExpiry(valueIn, json), json); + Date date; + + try +@@ -75,4 +75,28 @@ + data.addProperty("expires", this.banEndDate == null ? "forever" : DATE_FORMAT.format(this.banEndDate)); + data.addProperty("reason", this.reason); + } ++ ++ public String getSource() { ++ return this.bannedBy; ++ } ++ ++ public Date getCreated() { ++ return this.banStartDate; ++ } ++ ++ private static T checkExpiry(T object, JsonObject jsonobject) { ++ Date expires = null; ++ ++ try { ++ expires = jsonobject.has("expires") ? DATE_FORMAT.parse(jsonobject.get("expires").getAsString()) : null; ++ } catch (ParseException ex) { ++ // Guess we don't have a date ++ } ++ ++ if (expires == null || expires.after(new Date())) { ++ return object; ++ } else { ++ return null; ++ } ++ } + } diff --git a/patches/net/minecraft/server/network/NetHandlerHandshakeTCP.java.patch b/patches/net/minecraft/server/network/NetHandlerHandshakeTCP.java.patch new file mode 100644 index 00000000..717102cb --- /dev/null +++ b/patches/net/minecraft/server/network/NetHandlerHandshakeTCP.java.patch @@ -0,0 +1,128 @@ +--- ../src-base/minecraft/net/minecraft/server/network/NetHandlerHandshakeTCP.java ++++ ../src-work/minecraft/net/minecraft/server/network/NetHandlerHandshakeTCP.java +@@ -7,13 +7,21 @@ + import net.minecraft.network.login.server.SPacketDisconnect; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.text.ITextComponent; ++import net.minecraft.util.text.TextComponentString; + import net.minecraft.util.text.TextComponentTranslation; + ++import java.net.InetAddress; ++import java.util.HashMap; ++ + public class NetHandlerHandshakeTCP implements INetHandlerHandshakeServer + { + private final MinecraftServer server; + private final NetworkManager networkManager; + ++ private static final HashMap throttleTracker = new HashMap(); ++ private static int throttleCounter = 0; ++ private static final com.google.gson.Gson gson = new com.google.gson.Gson(); // Spigot ++ + public NetHandlerHandshakeTCP(MinecraftServer serverIn, NetworkManager netManager) + { + this.server = serverIn; +@@ -29,21 +37,101 @@ + case LOGIN: + this.networkManager.setConnectionState(EnumConnectionState.LOGIN); + ++ // CraftBukkit start - Connection throttle ++ try { ++ long currentTime = System.currentTimeMillis(); ++ long connectionThrottle = MinecraftServer.getServerCB().server.getConnectionThrottle(); ++ InetAddress address = ((java.net.InetSocketAddress) this.networkManager.getRemoteAddress()).getAddress(); ++ ++ synchronized (throttleTracker) { ++ if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { ++ throttleTracker.put(address, currentTime); ++ ITextComponent chatmessage = new TextComponentTranslation("Connection throttled! Please wait before reconnecting."); ++ this.networkManager.sendPacket(new SPacketDisconnect(chatmessage)); ++ this.networkManager.closeChannel(chatmessage); ++ return; ++ } ++ ++ throttleTracker.put(address, currentTime); ++ throttleCounter++; ++ if (throttleCounter > 200) { ++ throttleCounter = 0; ++ ++ // Cleanup stale entries ++ java.util.Iterator iter = throttleTracker.entrySet().iterator(); ++ while (iter.hasNext()) { ++ java.util.Map.Entry entry = (java.util.Map.Entry) iter.next(); ++ if (entry.getValue() > connectionThrottle) { ++ iter.remove(); ++ } ++ } ++ } ++ } ++ } catch (Throwable t) { ++ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); ++ } ++ // CraftBukkit end ++ + if (packetIn.getProtocolVersion() > 340) + { +- ITextComponent itextcomponent = new TextComponentTranslation("multiplayer.disconnect.outdated_server", new Object[] {"1.12.2"}); ++ ITextComponent itextcomponent = new TextComponentTranslation(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), new Object[] {"1.12.2"}); // Spigot + this.networkManager.sendPacket(new SPacketDisconnect(itextcomponent)); + this.networkManager.closeChannel(itextcomponent); + } + else if (packetIn.getProtocolVersion() < 340) + { +- ITextComponent itextcomponent1 = new TextComponentTranslation("multiplayer.disconnect.outdated_client", new Object[] {"1.12.2"}); ++ ITextComponent itextcomponent1 = new TextComponentTranslation(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), new Object[] {"1.12.2"}); // Spigot + this.networkManager.sendPacket(new SPacketDisconnect(itextcomponent1)); + this.networkManager.closeChannel(itextcomponent1); + } + else + { + this.networkManager.setNetHandler(new NetHandlerLoginServer(this.server, this.networkManager)); ++ // Paper start - handshake event ++ boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee; ++ boolean handledByEvent = false; ++ // Try and handle the handshake through the event ++ if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me? ++ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packetIn.ip, !proxyLogicEnabled); ++ if (event.callEvent()) { ++ // If we've failed somehow, let the client know so and go no further. ++ if (event.isFailed()) { ++ ITextComponent itextcomponent1 = new TextComponentTranslation(event.getFailMessage()); ++ this.networkManager.sendPacket(new SPacketDisconnect(itextcomponent1)); ++ this.networkManager.closeChannel(itextcomponent1); ++ return; ++ } ++ ++ packetIn.ip = event.getServerHostname(); ++ this.networkManager.socketAddress = new java.net.InetSocketAddress(event.getSocketAddressHostname(), ((java.net.InetSocketAddress) this.networkManager.getRemoteAddress()).getPort()); ++ this.networkManager.spoofedUUID = event.getUniqueId(); ++ this.networkManager.spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class); ++ handledByEvent = true; // Hooray, we did it! ++ } ++ } ++ // Don't try and handle default logic if it's been handled by the event. ++ if (!handledByEvent && proxyLogicEnabled) { ++ // Paper end ++ // Spigot Start ++ //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! ++ ++ String[] split = packetIn.ip.split("\00"); ++ if (split.length == 3 || split.length == 4) { ++ packetIn.ip = split[0]; ++ networkManager.socketAddress = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) networkManager.getRemoteAddress()).getPort()); ++ networkManager.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString(split[2]); ++ } else { ++ ITextComponent itextcomponent1 = new TextComponentTranslation("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"); ++ this.networkManager.sendPacket(new SPacketDisconnect(itextcomponent1)); ++ this.networkManager.closeChannel(itextcomponent1); ++ return; ++ } ++ if (split.length == 4) { ++ networkManager.spoofedProfile = gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class); ++ } ++ } ++ // Spigot End ++ ((NetHandlerLoginServer) this.networkManager.getNetHandler()).hostname = packetIn.ip + ":" + packetIn.port; // CraftBukkit - set hostname + } + + break; diff --git a/patches/net/minecraft/server/network/NetHandlerLoginServer.java.patch b/patches/net/minecraft/server/network/NetHandlerLoginServer.java.patch new file mode 100644 index 00000000..82d7c39b --- /dev/null +++ b/patches/net/minecraft/server/network/NetHandlerLoginServer.java.patch @@ -0,0 +1,297 @@ +--- ../src-base/minecraft/net/minecraft/server/network/NetHandlerLoginServer.java ++++ ../src-work/minecraft/net/minecraft/server/network/NetHandlerLoginServer.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.mojang.authlib.GameProfile; + import com.mojang.authlib.exceptions.AuthenticationUnavailableException; + import io.netty.channel.ChannelFuture; +@@ -29,10 +31,15 @@ + import net.minecraft.util.CryptManager; + import net.minecraft.util.ITickable; + import net.minecraft.util.text.ITextComponent; ++import net.minecraft.util.text.TextComponentString; + import net.minecraft.util.text.TextComponentTranslation; + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.event.player.AsyncPlayerPreLoginEvent; ++import org.bukkit.event.player.PlayerPreLoginEvent; + + public class NetHandlerLoginServer implements INetHandlerLoginServer, ITickable + { +@@ -42,13 +49,15 @@ + private final byte[] verifyToken = new byte[4]; + private final MinecraftServer server; + public final NetworkManager networkManager; +- private NetHandlerLoginServer.LoginState currentLoginState = NetHandlerLoginServer.LoginState.HELLO; ++ private LoginState currentLoginState = LoginState.HELLO; + private int connectionTimer; + private GameProfile loginGameProfile; + private final String serverId = ""; + private SecretKey secretKey; + private EntityPlayerMP player; + ++ public String hostname = ""; ++ + public NetHandlerLoginServer(MinecraftServer serverIn, NetworkManager networkManagerIn) + { + this.server = serverIn; +@@ -58,17 +67,21 @@ + + public void update() + { +- if (this.currentLoginState == NetHandlerLoginServer.LoginState.READY_TO_ACCEPT) ++ if (this.currentLoginState == LoginState.READY_TO_ACCEPT) + { ++ // Paper start - prevent logins to be processed even though disconnect was called ++ if (networkManager.isChannelOpen()) { + this.tryAcceptPlayer(); + } +- else if (this.currentLoginState == NetHandlerLoginServer.LoginState.DELAY_ACCEPT) ++ // Paper end ++ } ++ else if (this.currentLoginState == LoginState.DELAY_ACCEPT) + { + EntityPlayerMP entityplayermp = this.server.getPlayerList().getPlayerByUUID(this.loginGameProfile.getId()); + + if (entityplayermp == null) + { +- this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; ++ this.currentLoginState = LoginState.READY_TO_ACCEPT; + net.minecraftforge.fml.common.network.internal.FMLNetworkHandler.fmlServerHandshake(this.server.getPlayerList(), this.networkManager, this.player); + this.player = null; + } +@@ -94,22 +107,54 @@ + } + } + ++ // Spigot start ++ public void initUUID() ++ { ++ UUID uuid; ++ if ( networkManager.spoofedUUID != null ) ++ { ++ uuid = networkManager.spoofedUUID; ++ } else ++ { ++ uuid = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + this.loginGameProfile.getName() ).getBytes( StandardCharsets.UTF_8 ) ); ++ } ++ ++ this.loginGameProfile = new GameProfile( uuid, this.loginGameProfile.getName() ); ++ ++ if (networkManager.spoofedProfile != null) ++ { ++ for ( com.mojang.authlib.properties.Property property : networkManager.spoofedProfile ) ++ { ++ this.loginGameProfile.getProperties().put( property.getName(), property ); ++ } ++ } ++ ++ this.loginGameProfile = new GameProfile( uuid, this.loginGameProfile.getName() ); ++ } ++ // Spigot end ++ + public void tryAcceptPlayer() + { ++ // Spigot start - Moved to initUUID ++ /* + if (!this.loginGameProfile.isComplete()) + { + this.loginGameProfile = this.getOfflineProfile(this.loginGameProfile); + } ++ */ ++ // Spigot end + +- String s = this.server.getPlayerList().allowUserToConnect(this.networkManager.getRemoteAddress(), this.loginGameProfile); ++ // String s = this.server.getPlayerList().allowUserToConnect(this.networkManager.getRemoteAddress(), this.loginGameProfile); ++ // CraftBukkit start - fire PlayerLoginEvent ++ EntityPlayerMP s = this.server.getPlayerList().allowUserToConnect(this, this.loginGameProfile, hostname); + +- if (s != null) ++ if (s == null) + { +- this.disconnect(new TextComponentTranslation(s, new Object[0])); ++ // this.disconnect(new TextComponentTranslation(s, new Object[0])); + } + else + { +- this.currentLoginState = NetHandlerLoginServer.LoginState.ACCEPTED; ++ this.currentLoginState = LoginState.ACCEPTED; + + if (this.server.getNetworkCompressionThreshold() >= 0 && !this.networkManager.isLocalChannel()) + { +@@ -127,12 +172,12 @@ + + if (entityplayermp != null) + { +- this.currentLoginState = NetHandlerLoginServer.LoginState.DELAY_ACCEPT; +- this.player = this.server.getPlayerList().createPlayerForUser(this.loginGameProfile); ++ this.currentLoginState = LoginState.DELAY_ACCEPT; ++ this.player = this.server.getPlayerList().createPlayerForUser(this.loginGameProfile, s); + } + else + { +- net.minecraftforge.fml.common.network.internal.FMLNetworkHandler.fmlServerHandshake(this.server.getPlayerList(), this.networkManager, this.server.getPlayerList().createPlayerForUser(this.loginGameProfile)); ++ net.minecraftforge.fml.common.network.internal.FMLNetworkHandler.fmlServerHandshake(this.server.getPlayerList(), this.networkManager, s); + } + } + } +@@ -149,23 +194,42 @@ + + public void processLoginStart(CPacketLoginStart packetIn) + { +- Validate.validState(this.currentLoginState == NetHandlerLoginServer.LoginState.HELLO, "Unexpected hello packet"); ++ Validate.validState(this.currentLoginState == LoginState.HELLO, "Unexpected hello packet"); + this.loginGameProfile = packetIn.getProfile(); + + if (this.server.isServerInOnlineMode() && !this.networkManager.isLocalChannel()) + { +- this.currentLoginState = NetHandlerLoginServer.LoginState.KEY; ++ this.currentLoginState = LoginState.KEY; + this.networkManager.sendPacket(new SPacketEncryptionRequest("", this.server.getKeyPair().getPublic(), this.verifyToken)); + } + else + { +- this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; ++ // Spigot start ++ new Thread(net.minecraftforge.fml.common.thread.SidedThreadGroups.SERVER,"User Authenticator #" + NetHandlerLoginServer.AUTHENTICATOR_THREAD_ID.incrementAndGet()) ++ { ++ ++ @Override ++ public void run() ++ { ++ try ++ { ++ initUUID(); ++ new LoginHandler().fireEvents(); ++ } ++ catch (Exception ex) ++ { ++ disconnect(new TextComponentString("Failed to verify username!")); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + loginGameProfile.getName(), ex); ++ } ++ } ++ }.start(); ++ // Spigot end + } + } + + public void processEncryptionResponse(CPacketEncryptionResponse packetIn) + { +- Validate.validState(this.currentLoginState == NetHandlerLoginServer.LoginState.KEY, "Unexpected key packet"); ++ Validate.validState(this.currentLoginState == LoginState.KEY, "Unexpected key packet"); + PrivateKey privatekey = this.server.getKeyPair().getPrivate(); + + if (!Arrays.equals(this.verifyToken, packetIn.getVerifyToken(privatekey))) +@@ -175,7 +239,7 @@ + else + { + this.secretKey = packetIn.getSecretKey(privatekey); +- this.currentLoginState = NetHandlerLoginServer.LoginState.AUTHENTICATING; ++ this.currentLoginState = LoginState.AUTHENTICATING; + this.networkManager.enableEncryption(this.secretKey); + (new Thread(net.minecraftforge.fml.common.thread.SidedThreadGroups.SERVER, "User Authenticator #" + AUTHENTICATOR_THREAD_ID.incrementAndGet()) + { +@@ -190,14 +254,18 @@ + + if (NetHandlerLoginServer.this.loginGameProfile != null) + { +- NetHandlerLoginServer.LOGGER.info("UUID of player {} is {}", NetHandlerLoginServer.this.loginGameProfile.getName(), NetHandlerLoginServer.this.loginGameProfile.getId()); +- NetHandlerLoginServer.this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; ++ // CraftBukkit start - fire PlayerPreLoginEvent ++ if (!networkManager.isChannelOpen()) { ++ return; ++ } ++ ++ new LoginHandler().fireEvents(); + } + else if (NetHandlerLoginServer.this.server.isSinglePlayer()) + { + NetHandlerLoginServer.LOGGER.warn("Failed to verify username but will let them in anyway!"); + NetHandlerLoginServer.this.loginGameProfile = NetHandlerLoginServer.this.getOfflineProfile(gameprofile); +- NetHandlerLoginServer.this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; ++ NetHandlerLoginServer.this.currentLoginState = LoginState.READY_TO_ACCEPT; + } + else + { +@@ -211,13 +279,19 @@ + { + NetHandlerLoginServer.LOGGER.warn("Authentication servers are down but will let them in anyway!"); + NetHandlerLoginServer.this.loginGameProfile = NetHandlerLoginServer.this.getOfflineProfile(gameprofile); +- NetHandlerLoginServer.this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; ++ NetHandlerLoginServer.this.currentLoginState = LoginState.READY_TO_ACCEPT; + } + else + { + NetHandlerLoginServer.this.disconnect(new TextComponentTranslation("multiplayer.disconnect.authservers_down", new Object[0])); + NetHandlerLoginServer.LOGGER.error("Couldn't verify username because servers are unavailable"); ++ + } ++ // CraftBukkit start - catch all exceptions ++ } catch (Exception exception) { ++ disconnect(new TextComponentTranslation("Failed to verify username!")); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + gameprofile.getName(), exception); ++ // CraftBukkit end + } + } + @Nullable +@@ -230,6 +304,54 @@ + } + } + ++ // Spigot start ++ public class LoginHandler { ++ public void fireEvents() throws Exception { ++ String playerName = loginGameProfile.getName(); ++ java.net.InetAddress address = ((java.net.InetSocketAddress) networkManager.getRemoteAddress()).getAddress(); ++ java.util.UUID uniqueId = loginGameProfile.getId(); ++ final org.bukkit.craftbukkit.CraftServer server = NetHandlerLoginServer.this.server.server; ++ ++ // Paper start ++ PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); ++ server.getPluginManager().callEvent(asyncEvent); ++ profile = asyncEvent.getPlayerProfile(); ++ profile.complete(); ++ loginGameProfile = CraftPlayerProfile.asAuthlibCopy(profile); ++ playerName = loginGameProfile.getName(); ++ uniqueId = loginGameProfile.getId(); ++ // Paper end ++ ++ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { ++ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); ++ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { ++ event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); ++ } ++ Waitable waitable = new Waitable() { ++ @Override ++ protected PlayerPreLoginEvent.Result evaluate() { ++ server.getPluginManager().callEvent(event); ++ return event.getResult(); ++ }}; ++ NetHandlerLoginServer.this.server.processQueue.add(waitable); ++ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { ++ disconnect(new TextComponentString(event.getKickMessage())); ++ return; ++ } ++ } else { ++ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { ++ disconnect(new TextComponentString(asyncEvent.getKickMessage())); ++ return; ++ } ++ } ++ // CraftBukkit end ++ NetHandlerLoginServer.LOGGER.info("UUID of player {} is {}", NetHandlerLoginServer.this.loginGameProfile.getName(), NetHandlerLoginServer.this.loginGameProfile.getId()); ++ NetHandlerLoginServer.this.currentLoginState = LoginState.READY_TO_ACCEPT; ++ } ++ } ++ // Spigot end ++ + protected GameProfile getOfflineProfile(GameProfile original) + { + UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + original.getName()).getBytes(StandardCharsets.UTF_8)); diff --git a/patches/net/minecraft/server/network/NetHandlerStatusServer.java.patch b/patches/net/minecraft/server/network/NetHandlerStatusServer.java.patch new file mode 100644 index 00000000..52358bbe --- /dev/null +++ b/patches/net/minecraft/server/network/NetHandlerStatusServer.java.patch @@ -0,0 +1,130 @@ +--- ../src-base/minecraft/net/minecraft/server/network/NetHandlerStatusServer.java ++++ ../src-work/minecraft/net/minecraft/server/network/NetHandlerStatusServer.java +@@ -1,6 +1,9 @@ + package net.minecraft.server.network; + ++import com.mojang.authlib.GameProfile; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.network.NetworkManager; ++import net.minecraft.network.ServerStatusResponse; + import net.minecraft.network.status.INetHandlerStatusServer; + import net.minecraft.network.status.client.CPacketPing; + import net.minecraft.network.status.client.CPacketServerQuery; +@@ -9,7 +12,13 @@ + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentString; ++import net.minecraft.util.text.TextComponentTranslation; ++import org.bukkit.craftbukkit.util.CraftIconCache; ++import org.bukkit.entity.Player; + ++import java.net.InetSocketAddress; ++import java.util.Iterator; ++ + public class NetHandlerStatusServer implements INetHandlerStatusServer + { + private static final ITextComponent EXIT_MESSAGE = new TextComponentString("Status request has been handled."); +@@ -36,8 +45,102 @@ + else + { + this.handled = true; +- this.networkManager.sendPacket(new SPacketServerInfo(this.server.getServerStatusResponse())); ++ // CraftBukkit start ++ // this.networkManager.sendPacket(new SPacketServerInfo(this.server.getServerStatusResponse())); ++ final Object[] players = server.getPlayerList().getPlayers().toArray(); ++ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent { ++ ++ CraftIconCache icon = server.server.getServerIcon(); ++ ++ ServerListPingEvent() { ++ super(((InetSocketAddress) networkManager.getRemoteAddress()).getAddress(), server.getMOTD(), server.getPlayerList().getMaxPlayers()); ++ } ++ ++ @Override ++ public void setServerIcon(org.bukkit.util.CachedServerIcon icon) { ++ if (!(icon instanceof CraftIconCache)) { ++ throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class); ++ } ++ this.icon = (CraftIconCache) icon; ++ } ++ ++ @Override ++ public Iterator iterator() throws UnsupportedOperationException { ++ return new Iterator() { ++ int i; ++ int ret = Integer.MIN_VALUE; ++ EntityPlayerMP player; ++ ++ @Override ++ public boolean hasNext() { ++ if (player != null) { ++ return true; ++ } ++ final Object[] currentPlayers = players; ++ for (int length = currentPlayers.length, i = this.i; i < length; i++) { ++ final EntityPlayerMP player = (EntityPlayerMP) currentPlayers[i]; ++ if (player != null) { ++ this.i = i + 1; ++ this.player = player; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Player next() { ++ if (!hasNext()) { ++ throw new java.util.NoSuchElementException(); ++ } ++ final EntityPlayerMP player = this.player; ++ this.player = null; ++ this.ret = this.i - 1; ++ return player.getBukkitEntity(); ++ } ++ ++ @Override ++ public void remove() { ++ final Object[] currentPlayers = players; ++ final int i = this.ret; ++ if (i < 0 || currentPlayers[i] == null) { ++ throw new IllegalStateException(); ++ } ++ currentPlayers[i] = null; ++ } ++ }; ++ } ++ } ++ ++ ServerListPingEvent event = new ServerListPingEvent(); ++ this.server.server.getPluginManager().callEvent(event); ++ ++ java.util.List profiles = new java.util.ArrayList(players.length); ++ for (Object player : players) { ++ if (player != null) { ++ profiles.add(((EntityPlayerMP) player).getGameProfile()); ++ } ++ } ++ ++ ServerStatusResponse.Players playerSample = new ServerStatusResponse.Players(event.getMaxPlayers(), profiles.size()); ++ // Spigot Start ++ if (!profiles.isEmpty()) ++ { ++ java.util.Collections.shuffle(profiles); // This sucks, its inefficient but we have no simple way of doing it differently ++ profiles = profiles.subList(0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample)); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour ++ } ++ // Spigot End ++ ++ ServerStatusResponse ping = new ServerStatusResponse(); ++ ping.setFavicon(event.icon.value); ++ ping.setServerDescription(new TextComponentTranslation(event.getMotd())); ++ ping.setPlayers(playerSample); ++ int version = server.getServerStatusResponse().getVersion().getProtocol(); ++ ping.setVersion(new ServerStatusResponse.Version(server.getMinecraftVersion(), version)); ++ ++ this.networkManager.sendPacket(new SPacketServerInfo(ping)); + } ++ // CraftBukkit end + } + + public void processPing(CPacketPing packetIn) diff --git a/patches/net/minecraft/stats/RecipeBookServer.java.patch b/patches/net/minecraft/stats/RecipeBookServer.java.patch new file mode 100644 index 00000000..f8d056d2 --- /dev/null +++ b/patches/net/minecraft/stats/RecipeBookServer.java.patch @@ -0,0 +1,10 @@ +--- ../src-base/minecraft/net/minecraft/stats/RecipeBookServer.java ++++ ../src-work/minecraft/net/minecraft/stats/RecipeBookServer.java +@@ -55,6 +55,7 @@ + + private void sendPacket(SPacketRecipeBook.State state, EntityPlayerMP player, List recipesIn) + { ++ if (player.connection == null) return; // SPIGOT-4478 during PlayerLoginEvent + net.minecraftforge.common.ForgeHooks.sendRecipeBook(player.connection, state, recipesIn, Collections.emptyList(), this.isGuiOpen, this.isFilteringCraftable); + } + diff --git a/patches/net/minecraft/stats/StatisticsManager.java.patch b/patches/net/minecraft/stats/StatisticsManager.java.patch new file mode 100644 index 00000000..f8107cbf --- /dev/null +++ b/patches/net/minecraft/stats/StatisticsManager.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/stats/StatisticsManager.java ++++ ../src-work/minecraft/net/minecraft/stats/StatisticsManager.java +@@ -11,6 +11,10 @@ + + public void increaseStat(EntityPlayer player, StatBase stat, int amount) + { ++ org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.readStat(stat), amount); ++ if (cancellable != null && cancellable.isCancelled()) { ++ return; ++ } + this.unlockAchievement(player, stat, this.readStat(stat) + amount); + } + diff --git a/patches/net/minecraft/stats/StatisticsManagerServer.java.patch b/patches/net/minecraft/stats/StatisticsManagerServer.java.patch new file mode 100644 index 00000000..5cc1b203 --- /dev/null +++ b/patches/net/minecraft/stats/StatisticsManagerServer.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/stats/StatisticsManagerServer.java ++++ ../src-work/minecraft/net/minecraft/stats/StatisticsManagerServer.java +@@ -34,6 +34,14 @@ + { + this.mcServer = serverIn; + this.statsFile = statsFileIn; ++ // Spigot start ++ for (String name : org.spigotmc.SpigotConfig.forcedStats.keySet()) ++ { ++ TupleIntJsonSerializable wrapper = new TupleIntJsonSerializable(); ++ wrapper.setIntegerValue(org.spigotmc.SpigotConfig.forcedStats.get(name)); ++ statsData.put(StatList.getOneShotStat(name), wrapper); ++ } ++ // Spigot end + } + + public void readStatFile() +@@ -58,6 +66,7 @@ + + public void saveStatFile() + { ++ if (org.spigotmc.SpigotConfig.disableStatSaving) return; // Spigot + try + { + FileUtils.writeStringToFile(this.statsFile, dumpJson(this.statsData)); diff --git a/patches/net/minecraft/tileentity/CommandBlockBaseLogic.java.patch b/patches/net/minecraft/tileentity/CommandBlockBaseLogic.java.patch new file mode 100644 index 00000000..6adfeeda --- /dev/null +++ b/patches/net/minecraft/tileentity/CommandBlockBaseLogic.java.patch @@ -0,0 +1,231 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/CommandBlockBaseLogic.java ++++ ../src-work/minecraft/net/minecraft/tileentity/CommandBlockBaseLogic.java +@@ -1,23 +1,40 @@ + package net.minecraft.tileentity; + ++import com.google.common.base.Joiner; + import io.netty.buffer.ByteBuf; + import java.text.SimpleDateFormat; ++import java.util.ArrayList; + import java.util.Date; ++import java.util.logging.Level; + import javax.annotation.Nullable; ++ ++import net.minecraft.advancements.FunctionManager; ++import net.minecraft.command.CommandException; + import net.minecraft.command.CommandResultStats; ++import net.minecraft.command.CommandSenderWrapper; ++import net.minecraft.command.EntitySelector; + import net.minecraft.command.ICommandSender; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.crash.ICrashReportDetail; ++import net.minecraft.entity.item.EntityMinecartCommandBlock; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.rcon.RConConsoleSource; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.util.ReportedException; + import net.minecraft.util.text.ITextComponent; + import net.minecraft.util.text.TextComponentString; ++import net.minecraft.util.text.TextComponentTranslation; ++import net.minecraft.util.text.TextFormatting; + import net.minecraft.world.World; ++import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++import org.bukkit.event.server.ServerCommandEvent; + + public abstract class CommandBlockBaseLogic implements ICommandSender + { +@@ -30,6 +47,7 @@ + private String commandStored = ""; + private String customName = "@"; + private final CommandResultStats resultStats = new CommandResultStats(); ++ protected CommandSender sender; + + public int getSuccessCount() + { +@@ -152,7 +170,8 @@ + try + { + this.lastOutput = null; +- this.successCount = minecraftserver.getCommandManager().executeCommand(this, this.commandStored); ++// this.successCount = minecraftserver.getCommandManager().executeCommand(this, this.commandStored); ++ this.successCount = executeSafely(this, sender, this.commandStored); + } + catch (Throwable throwable) + { +@@ -198,6 +217,169 @@ + } + } + ++ public static int executeSafely(ICommandSender sender, CommandSender bSender, String command) { ++ try { ++ return executeCommand(sender, bSender, command); ++ } catch (CommandException commandexception) { ++ // Taken from CommandHandler ++ TextComponentTranslation chatmessage = new TextComponentTranslation(commandexception.getMessage(), commandexception.getErrorObjects()); ++ chatmessage.getStyle().setColor(TextFormatting.RED); ++ sender.sendMessage(chatmessage); ++ } ++ ++ return 0; ++ } ++ ++ public static int executeCommand(ICommandSender sender, CommandSender bSender, String command) throws CommandException { ++ org.bukkit.command.SimpleCommandMap commandMap = sender.getEntityWorld().getServer().getCommandMap(); ++ Joiner joiner = Joiner.on(" "); ++ if (command.startsWith("/")) { ++ command = command.substring(1); ++ } ++ ++ ServerCommandEvent event = new ServerCommandEvent(bSender, command); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return 0; ++ } ++ command = event.getCommand(); ++ ++ String[] args = command.split(" "); ++ ArrayList commands = new ArrayList(); ++ ++ String cmd = args[0]; ++ if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); ++ if (cmd.startsWith("bukkit:")) cmd = cmd.substring("bukkit:".length()); ++ ++ // Block disallowed commands ++ if (cmd.equalsIgnoreCase("stop") || cmd.equalsIgnoreCase("kick") || cmd.equalsIgnoreCase("op") ++ || cmd.equalsIgnoreCase("deop") || cmd.equalsIgnoreCase("ban") || cmd.equalsIgnoreCase("ban-ip") ++ || cmd.equalsIgnoreCase("pardon") || cmd.equalsIgnoreCase("pardon-ip") || cmd.equalsIgnoreCase("reload")) { ++ return 0; ++ } ++ ++ // Handle vanilla commands; ++ org.bukkit.command.Command commandBlockCommand = commandMap.getCommand(args[0]); ++ if (sender.getEntityWorld().getServer().getCommandBlockOverride(args[0])) { ++ commandBlockCommand = commandMap.getCommand("minecraft:" + args[0]); ++ } ++ if (commandBlockCommand instanceof VanillaCommandWrapper) { ++ command = command.trim(); ++ if (command.startsWith("/")) { ++ command = command.substring(1); ++ } ++ String as[] = command.split(" "); ++ as = VanillaCommandWrapper.dropFirstArgument(as); ++ if (!sender.getEntityWorld().getServer().getPermissionOverride(sender) && !((VanillaCommandWrapper) commandBlockCommand).testPermission(bSender)) { ++ return 0; ++ } ++ return ((VanillaCommandWrapper) commandBlockCommand).dispatchVanillaCommand(bSender, sender, as); ++ } ++ ++ // Make sure this is a valid command ++ if (commandMap.getCommand(args[0]) == null) { ++ return 0; ++ } ++ ++ commands.add(args); ++ ++ // Find positions of command block syntax, if any ++ WorldServer[] prev = MinecraftServer.getServerCB().worlds; ++ MinecraftServer server = MinecraftServer.getServerCB(); ++ server.worlds = new WorldServer[server.worldServerList.size()]; ++ server.worlds[0] = (WorldServer) sender.getEntityWorld(); ++ int bpos = 0; ++ for (int pos = 1; pos < server.worlds.length; pos++) { ++ WorldServer world = server.worldServerList.get(bpos++); ++ if (server.worlds[0] == world) { ++ pos--; ++ continue; ++ } ++ server.worlds[pos] = world; ++ } ++ try { ++ ArrayList newCommands = new ArrayList(); ++ for (int i = 0; i < args.length; i++) { ++ if (EntitySelector.isSelectorDefault(args[i])) { ++ for (int j = 0; j < commands.size(); j++) { ++ newCommands.addAll(buildCommands(sender, commands.get(j), i)); ++ } ++ ArrayList temp = commands; ++ commands = newCommands; ++ newCommands = temp; ++ newCommands.clear(); ++ } ++ } ++ } finally { ++ MinecraftServer.getServerCB().worlds = prev; ++ } ++ ++ int completed = 0; ++ ++ // Now dispatch all of the commands we ended up with ++ for (int i = 0; i < commands.size(); i++) { ++ try { ++ if (commandMap.dispatch(bSender, joiner.join(java.util.Arrays.asList(commands.get(i))))) { ++ completed++; ++ } ++ } catch (Throwable exception) { ++ if (sender.getCommandSenderEntity() instanceof EntityMinecartCommandBlock) { ++ MinecraftServer.getServerCB().server.getLogger().log(Level.WARNING, String.format("MinecartCommandBlock at (%d,%d,%d) failed to handle command", sender.getPosition().getX(), sender.getPosition().getY(), sender.getPosition().getZ()), exception); ++ } else if (sender instanceof CommandBlockBaseLogic) { ++ CommandBlockBaseLogic listener = (CommandBlockBaseLogic) sender; ++ MinecraftServer.getServerCB().server.getLogger().log(Level.WARNING, String.format("CommandBlock at (%d,%d,%d) failed to handle command", listener.getPosition().getX(), listener.getPosition().getY(), listener.getPosition().getZ()), exception); ++ } else { ++ MinecraftServer.getServerCB().server.getLogger().log(Level.WARNING, String.format("Unknown CommandBlock failed to handle command"), exception); ++ } ++ } ++ } ++ ++ return completed; ++ } ++ ++ private static ArrayList buildCommands(ICommandSender sender, String[] args, int pos) throws CommandException { ++ ArrayList commands = new ArrayList(); ++ java.util.List players = (java.util.List) EntitySelector.matchEntitiesDefault(sender, args[pos], EntityPlayer.class); ++ ++ if (players != null) { ++ for (EntityPlayer player : players) { ++ if (player.world != sender.getEntityWorld()) { ++ continue; ++ } ++ String[] command = args.clone(); ++ command[pos] = player.getName(); ++ commands.add(command); ++ } ++ } ++ ++ return commands; ++ } ++ ++ public static CommandSender unwrapSender(ICommandSender listener) { ++ CommandSender sender = null; ++ while (sender == null) { ++ if (listener instanceof DedicatedServer) { ++ sender = ((DedicatedServer) listener).console; ++ } else if (listener instanceof RConConsoleSource) { ++ sender = ((RConConsoleSource) listener).getServer().remoteConsole; ++ } else if (listener instanceof CommandBlockBaseLogic) { ++ sender = ((CommandBlockBaseLogic) listener).sender; ++ } else if (listener instanceof FunctionManager.CustomFunctionListener) { ++ sender = ((FunctionManager.CustomFunctionListener) listener).sender; ++ } else if (listener instanceof CommandSenderWrapper) { ++ listener = ((CommandSenderWrapper) listener).delegate; // Search deeper ++ } else if (VanillaCommandWrapper.lastSender != null) { ++ sender = VanillaCommandWrapper.lastSender; ++ } else if (listener.getCommandSenderEntity() != null) { ++ sender = listener.getCommandSenderEntity().getBukkitEntity(); ++ } else { ++ throw new RuntimeException("Unhandled executor " + listener.getClass().getSimpleName()); ++ } ++ } ++ ++ return sender; ++ } ++ + public String getName() + { + return this.customName; diff --git a/patches/net/minecraft/tileentity/MobSpawnerBaseLogic.java.patch b/patches/net/minecraft/tileentity/MobSpawnerBaseLogic.java.patch new file mode 100644 index 00000000..59724a54 --- /dev/null +++ b/patches/net/minecraft/tileentity/MobSpawnerBaseLogic.java.patch @@ -0,0 +1,66 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/MobSpawnerBaseLogic.java ++++ ../src-work/minecraft/net/minecraft/tileentity/MobSpawnerBaseLogic.java +@@ -22,21 +22,21 @@ + + public abstract class MobSpawnerBaseLogic + { +- private int spawnDelay = 20; ++ public int spawnDelay = 20; + private final List potentialSpawns = Lists.newArrayList(); + private WeightedSpawnerEntity spawnData = new WeightedSpawnerEntity(); + private double mobRotation; + private double prevMobRotation; +- private int minSpawnDelay = 200; +- private int maxSpawnDelay = 800; +- private int spawnCount = 4; ++ public int minSpawnDelay = 200; ++ public int maxSpawnDelay = 800; ++ public int spawnCount = 4; + private Entity cachedEntity; +- private int maxNearbyEntities = 6; +- private int activatingRangeFromPlayer = 16; +- private int spawnRange = 4; ++ public int maxNearbyEntities = 6; // CraftBukkit private -> public ++ public int activatingRangeFromPlayer = 16; // CraftBukkit private -> public ++ public int spawnRange = 4; // CraftBukkit private -> public + + @Nullable +- private ResourceLocation getEntityId() ++ public ResourceLocation getEntityId() + { + String s = this.spawnData.getNbt().getString("id"); + return StringUtils.isNullOrEmpty(s) ? null : new ResourceLocation(s); +@@ -47,6 +47,7 @@ + if (id != null) + { + this.spawnData.getNbt().setString("id", id.toString()); ++ this.potentialSpawns.clear(); // CraftBukkit - SPIGOT-3496, MC-92282 + } + } + +@@ -132,7 +133,15 @@ + ((EntityLiving)entity).onInitialSpawn(world.getDifficultyForLocation(new BlockPos(entity)), (IEntityLivingData)null); + } + +- AnvilChunkLoader.spawnEntity(entity, world); ++ if (entity.world.spigotConfig.nerfSpawnerMobs) entity.fromMobSpawner = true; // Spigot Start ++ ++ flag = true; // Paper ++ ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockpos).isCancelled()) { ++ continue; ++ } ++ ++ AnvilChunkLoader.spawnEntity(entity, world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER); + world.playEvent(2004, blockpos, 0); + + if (entityliving != null) +@@ -140,7 +149,7 @@ + entityliving.spawnExplosionParticle(); + } + +- flag = true; ++ /*flag = true;*/ // Paper - moved up above cancellable event + } + } + diff --git a/patches/net/minecraft/tileentity/TileEntity.java.patch b/patches/net/minecraft/tileentity/TileEntity.java.patch new file mode 100644 index 00000000..0ec9b9a4 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntity.java.patch @@ -0,0 +1,42 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntity.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntity.java +@@ -20,12 +20,16 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.spigotmc.CustomTimingsHandler; // Spigot ++import org.bukkit.inventory.InventoryHolder; + ++import org.spigotmc.CustomTimingsHandler; ++ + public abstract class TileEntity implements net.minecraftforge.common.capabilities.ICapabilitySerializable + { + private static final Logger LOGGER = LogManager.getLogger(); + private static final RegistryNamespaced < ResourceLocation, Class > REGISTRY = new RegistryNamespaced < ResourceLocation, Class > (); +- protected World world; ++ public World world; + protected BlockPos pos = BlockPos.ORIGIN; + protected boolean tileEntityInvalid; + private int blockMetadata = -1; +@@ -315,7 +319,7 @@ + * @param net The NetworkManager the packet originated from + * @param pkt The data packet + */ +- public void onDataPacket(net.minecraft.network.NetworkManager net, net.minecraft.network.play.server.SPacketUpdateTileEntity pkt) ++ public void onDataPacket(net.minecraft.network.NetworkManager net, SPacketUpdateTileEntity pkt) + { + } + +@@ -531,4 +535,12 @@ + register("shulker_box", TileEntityShulkerBox.class); + register("bed", TileEntityBed.class); + } ++ ++ @Nullable ++ public InventoryHolder getOwner() { ++ if (world == null) return null; ++ org.bukkit.block.BlockState state = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); ++ if (state instanceof InventoryHolder) return (InventoryHolder) state; ++ return null; ++ } + } diff --git a/patches/net/minecraft/tileentity/TileEntityBanner.java.patch b/patches/net/minecraft/tileentity/TileEntityBanner.java.patch new file mode 100644 index 00000000..352d60cd --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityBanner.java.patch @@ -0,0 +1,33 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityBanner.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityBanner.java +@@ -19,8 +19,8 @@ + public class TileEntityBanner extends TileEntity implements IWorldNameable + { + private String name; +- private EnumDyeColor baseColor = EnumDyeColor.BLACK; +- private NBTTagList patterns; ++ public EnumDyeColor baseColor = EnumDyeColor.BLACK; ++ public NBTTagList patterns; + private boolean patternDataSet; + private List patternList; + private List colorList; +@@ -34,6 +34,9 @@ + if (nbttagcompound != null && nbttagcompound.hasKey("Patterns", 9)) + { + this.patterns = nbttagcompound.getTagList("Patterns", 10).copy(); ++ while (this.patterns.tagCount() > 20) { ++ this.patterns.removeTag(20); ++ } + } + + this.baseColor = p_175112_2_ ? getColor(stack) : ItemBanner.getBaseColor(stack); +@@ -88,6 +91,9 @@ + + this.baseColor = EnumDyeColor.byDyeDamage(compound.getInteger("Base")); + this.patterns = compound.getTagList("Patterns", 10); ++ while (this.patterns.tagCount() > 20) { ++ this.patterns.removeTag(20); ++ } + this.patternList = null; + this.colorList = null; + this.patternResourceLocation = null; diff --git a/patches/net/minecraft/tileentity/TileEntityBeacon.java.patch b/patches/net/minecraft/tileentity/TileEntityBeacon.java.patch new file mode 100644 index 00000000..d0a5c543 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityBeacon.java.patch @@ -0,0 +1,216 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityBeacon.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityBeacon.java +@@ -16,7 +16,6 @@ + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.entity.player.InventoryPlayer; + import net.minecraft.init.Blocks; +-import net.minecraft.init.Items; + import net.minecraft.init.MobEffects; + import net.minecraft.inventory.Container; + import net.minecraft.inventory.ContainerBeacon; +@@ -33,29 +32,62 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.potion.CraftPotionUtil; ++import org.bukkit.entity.HumanEntity; + +-public class TileEntityBeacon extends TileEntityLockable implements ITickable, ISidedInventory +-{ +- public static final Potion[][] EFFECTS_LIST = new Potion[][] {{MobEffects.SPEED, MobEffects.HASTE}, {MobEffects.RESISTANCE, MobEffects.JUMP_BOOST}, {MobEffects.STRENGTH}, {MobEffects.REGENERATION}}; ++public class TileEntityBeacon extends TileEntityLockable implements ITickable, ISidedInventory { ++ public static final Potion[][] EFFECTS_LIST = new Potion[][]{{MobEffects.SPEED, MobEffects.HASTE}, {MobEffects.RESISTANCE, MobEffects.JUMP_BOOST}, {MobEffects.STRENGTH}, {MobEffects.REGENERATION}}; + private static final Set VALID_EFFECTS = Sets.newHashSet(); +- private final List beamSegments = Lists.newArrayList(); ++ private final List beamSegments = Lists.newArrayList(); + @SideOnly(Side.CLIENT) + private long beamRenderCounter; + @SideOnly(Side.CLIENT) + private float beamRenderScale; + private boolean isComplete; +- private int levels = -1; ++ public int levels = -1; + @Nullable +- private Potion primaryEffect; ++ public Potion primaryEffect; + @Nullable +- private Potion secondaryEffect; ++ public Potion secondaryEffect; + private ItemStack payment = ItemStack.EMPTY; + private String customName; ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; + +- public void update() +- { +- if (this.world.getTotalWorldTime() % 80L == 0L) +- { ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public List getContents() { ++ return Arrays.asList(this.payment); ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ ++ @Nullable ++ public org.bukkit.potion.PotionEffect getPrimaryEffect() { ++ return (this.primaryEffect != null) ? CraftPotionUtil.toBukkit(new PotionEffect(this.primaryEffect, getLevel(), getAmplification(), true, true)) : null; ++ } ++ ++ @Nullable ++ public org.bukkit.potion.PotionEffect getSecondaryEffect() { ++ return (hasSecondaryEffect()) ? CraftPotionUtil.toBukkit(new PotionEffect(this.secondaryEffect, getLevel(), getAmplification(), true, true)) : null; ++ } ++ ++ public void update() { ++ if (this.world.getTotalWorldTime() % 80L == 0L) { + this.updateBeacon(); + } + } +@@ -69,39 +101,54 @@ + } + } + +- private void addEffectsToPlayers() +- { +- if (this.isComplete && this.levels > 0 && !this.world.isRemote && this.primaryEffect != null) +- { +- double d0 = (double)(this.levels * 10 + 10); +- int i = 0; ++ // CraftBukkit start - split into components ++ private byte getAmplification() { ++ byte i = 0; + +- if (this.levels >= 4 && this.primaryEffect == this.secondaryEffect) +- { +- i = 1; +- } ++ if (this.levels >= 4 && this.primaryEffect == this.secondaryEffect) { ++ i = 1; ++ } + +- int j = (9 + this.levels * 2) * 20; +- int k = this.pos.getX(); +- int l = this.pos.getY(); +- int i1 = this.pos.getZ(); +- AxisAlignedBB axisalignedbb = (new AxisAlignedBB((double)k, (double)l, (double)i1, (double)(k + 1), (double)(l + 1), (double)(i1 + 1))).grow(d0).expand(0.0D, (double)this.world.getHeight(), 0.0D); +- List list = this.world.getEntitiesWithinAABB(EntityPlayer.class, axisalignedbb); ++ return i; ++ } + +- for (EntityPlayer entityplayer : list) +- { +- entityplayer.addPotionEffect(new PotionEffect(this.primaryEffect, j, i, true, true)); +- } ++ private int getLevel() { ++ return (9 + this.levels * 2) * 20; ++ } + +- if (this.levels >= 4 && this.primaryEffect != this.secondaryEffect && this.secondaryEffect != null) +- { +- for (EntityPlayer entityplayer1 : list) +- { +- entityplayer1.addPotionEffect(new PotionEffect(this.secondaryEffect, j, 0, true, true)); +- } ++ public List getHumansInRange() { ++ double d0 = (double) (this.levels * 10 + 10); ++ int k = this.pos.getX(); ++ int l = this.pos.getY(); ++ int i1 = this.pos.getZ(); ++ AxisAlignedBB axisalignedbb = (new AxisAlignedBB((double)k, (double)l, (double)i1, (double)(k + 1), (double)(l + 1), (double)(i1 + 1))).grow(d0).expand(0.0D, (double)this.world.getHeight(), 0.0D); ++ return this.world.getEntitiesWithinAABB(EntityPlayer.class, axisalignedbb); ++ } ++ ++ private void applyEffect(List list, Potion effects, int i, int b0) { ++ for (EntityPlayer entityplayer : list) ++ entityplayer.addPotionEffect(new PotionEffect(effects, i, b0, true, true)); ++ } ++ ++ private boolean hasSecondaryEffect() { ++ return this.levels >= 4 && this.primaryEffect != this.secondaryEffect && this.secondaryEffect != null; ++ } ++ ++ private void addEffectsToPlayers() { ++ if (this.isComplete && this.levels > 0 && !this.world.isRemote && this.primaryEffect != null) { ++ byte b0 = getAmplification(); ++ ++ int i = getLevel(); ++ List list = getHumansInRange(); ++ ++ applyEffect(list, this.primaryEffect, i, b0); ++ ++ if (hasSecondaryEffect()) { ++ applyEffect(list, this.secondaryEffect, i, 0); + } + } + } ++ // CraftBukkit end + + private void updateSegmentColors() + { +@@ -112,7 +159,7 @@ + this.levels = 0; + this.beamSegments.clear(); + this.isComplete = true; +- TileEntityBeacon.BeamSegment tileentitybeacon$beamsegment = new TileEntityBeacon.BeamSegment(EnumDyeColor.WHITE.getColorComponentValues()); ++ BeamSegment tileentitybeacon$beamsegment = new BeamSegment(EnumDyeColor.WHITE.getColorComponentValues()); + this.beamSegments.add(tileentitybeacon$beamsegment); + boolean flag = true; + BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos(); +@@ -159,7 +206,7 @@ + } + else + { +- tileentitybeacon$beamsegment = new TileEntityBeacon.BeamSegment(afloat); ++ tileentitybeacon$beamsegment = new BeamSegment(afloat); + this.beamSegments.add(tileentitybeacon$beamsegment); + } + +@@ -215,7 +262,7 @@ + } + + @SideOnly(Side.CLIENT) +- public List getBeamSegments() ++ public List getBeamSegments() + { + return this.beamSegments; + } +@@ -285,8 +332,12 @@ + public void readFromNBT(NBTTagCompound compound) + { + super.readFromNBT(compound); +- this.primaryEffect = isBeaconEffect(compound.getInteger("Primary")); +- this.secondaryEffect = isBeaconEffect(compound.getInteger("Secondary")); ++ // Craftbukkit start - persist manually set non-default beacon effects (SPIGOT-3598) ++ // this.primaryEffect = isBeaconEffect(compound.getInteger("Primary")); ++ // this.secondaryEffect = isBeaconEffect(compound.getInteger("Secondary")); ++ this.primaryEffect = Potion.getPotionById(compound.getInteger("Primary")); ++ this.secondaryEffect = Potion.getPotionById(compound.getInteger("Secondary")); ++ // Craftbukkit end + this.levels = compound.getInteger("Levels"); + } + +@@ -374,7 +425,7 @@ + + public int getInventoryStackLimit() + { +- return 1; ++ return maxStack; + } + + public boolean isUsableByPlayer(EntityPlayer player) diff --git a/patches/net/minecraft/tileentity/TileEntityBrewingStand.java.patch b/patches/net/minecraft/tileentity/TileEntityBrewingStand.java.patch new file mode 100644 index 00000000..1e3eae0b --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityBrewingStand.java.patch @@ -0,0 +1,148 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityBrewingStand.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityBrewingStand.java +@@ -1,6 +1,8 @@ + package net.minecraft.tileentity; + + import java.util.Arrays; ++import java.util.List; ++ + import net.minecraft.block.BlockBrewingStand; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.player.EntityPlayer; +@@ -15,6 +17,7 @@ + import net.minecraft.item.ItemStack; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.potion.PotionHelper; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.EnumFacing; + import net.minecraft.util.ITickable; + import net.minecraft.util.NonNullList; +@@ -22,6 +25,12 @@ + import net.minecraft.util.datafix.FixTypes; + import net.minecraft.util.datafix.walkers.ItemStackDataLists; + import net.minecraft.util.math.BlockPos; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.inventory.BrewEvent; ++import org.bukkit.event.inventory.BrewingStandFuelEvent; ++import org.bukkit.inventory.InventoryHolder; + + public class TileEntityBrewingStand extends TileEntityLockable implements ITickable, ISidedInventory + { +@@ -35,6 +44,32 @@ + private String customName; + private int fuel; + ++ private int lastTick = MinecraftServer.currentTick; ++ private int maxStack = 64; ++ ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ ++ public List getContents() { ++ return this.brewingItemStacks; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ + public String getName() + { + return this.hasCustomName() ? this.customName : "container.brewing"; +@@ -74,8 +109,19 @@ + + if (this.fuel <= 0 && itemstack.getItem() == Items.BLAZE_POWDER) + { +- this.fuel = 20; +- itemstack.shrink(1); ++ // this.fuel = 20; ++ // itemstack.shrink(1); ++ BrewingStandFuelEvent event = new BrewingStandFuelEvent(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftItemStack.asCraftMirror(itemstack), 20); ++ this.world.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ this.fuel = event.getFuelPower(); ++ if (this.fuel > 0 && event.isConsuming()) { ++ itemstack.shrink(1); ++ } + this.markDirty(); + } + +@@ -83,10 +129,14 @@ + boolean flag1 = this.brewTime > 0; + ItemStack itemstack1 = this.brewingItemStacks.get(3); + ++ // CraftBukkit start - Use wall time instead of ticks for brewing ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ this.lastTick = MinecraftServer.currentTick; ++ + if (flag1) + { +- --this.brewTime; +- boolean flag2 = this.brewTime == 0; ++ this.brewTime -= elapsedTicks; ++ boolean flag2 = this.brewTime <= 0; // == -> <= + + if (flag2 && flag) + { +@@ -185,6 +235,15 @@ + if (net.minecraftforge.event.ForgeEventFactory.onPotionAttemptBrew(brewingItemStacks)) return; + ItemStack itemstack = this.brewingItemStacks.get(3); + ++ InventoryHolder owner = this.getOwner(); ++ if (owner != null) { ++ BrewEvent event = new BrewEvent(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), (org.bukkit.inventory.BrewerInventory) owner.getInventory(), this.fuel); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ + net.minecraftforge.common.brewing.BrewingRecipeRegistry.brewPotions(brewingItemStacks, brewingItemStacks.get(3), OUTPUT_SLOTS); + + itemstack.shrink(1); +@@ -269,7 +328,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return this.maxStack; + } + + public boolean isUsableByPlayer(EntityPlayer player) +@@ -377,14 +436,14 @@ + } + } + +- net.minecraftforge.items.IItemHandler handlerInput = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.UP); +- net.minecraftforge.items.IItemHandler handlerOutput = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.DOWN); +- net.minecraftforge.items.IItemHandler handlerSides = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.NORTH); ++ net.minecraftforge.items.IItemHandler handlerInput = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.UP); ++ net.minecraftforge.items.IItemHandler handlerOutput = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.DOWN); ++ net.minecraftforge.items.IItemHandler handlerSides = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.NORTH); + + @SuppressWarnings("unchecked") + @Override + @javax.annotation.Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @javax.annotation.Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @javax.annotation.Nullable EnumFacing facing) + { + if (facing != null && capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + { diff --git a/patches/net/minecraft/tileentity/TileEntityChest.java.patch b/patches/net/minecraft/tileentity/TileEntityChest.java.patch new file mode 100644 index 00000000..238c28f6 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityChest.java.patch @@ -0,0 +1,95 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityChest.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityChest.java +@@ -5,6 +5,7 @@ + import net.minecraft.block.BlockChest; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.InventoryPlayer; ++import net.minecraft.init.Blocks; + import net.minecraft.init.SoundEvents; + import net.minecraft.inventory.Container; + import net.minecraft.inventory.ContainerChest; +@@ -23,6 +24,10 @@ + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraft.util.math.BlockPos; + ++import java.util.List; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++ + public class TileEntityChest extends TileEntityLockableLoot implements ITickable + { + private NonNullList chestContents = NonNullList.withSize(27, ItemStack.EMPTY); +@@ -37,6 +42,8 @@ + private int ticksSinceSync; + private BlockChest.Type cachedChestType; + ++ private int maxStack = MAX_STACK; ++ + public TileEntityChest() + { + } +@@ -45,7 +52,27 @@ + { + this.cachedChestType = typeIn; + } ++ public List transaction = new java.util.ArrayList(); + ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ public List getContents() { ++ return this.chestContents; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ + public int getSizeInventory() + { + return 27; +@@ -109,7 +136,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public void updateContainingBlockInfo() +@@ -325,8 +352,18 @@ + this.numPlayersUsing = 0; + } + ++ int oldPower = Math.max(0, Math.min(15, this.numPlayersUsing)); // CraftBukkit - Get power before new viewer is added ++ + ++this.numPlayersUsing; ++ if (this.world == null) return; + this.world.addBlockEvent(this.pos, this.getBlockType(), 1, this.numPlayersUsing); ++ if (this.getBlockType() == Blocks.TRAPPED_CHEST) { ++ int newPower = Math.max(0, Math.min(15, this.numPlayersUsing)); ++ ++ if (oldPower != newPower) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos.getX(), pos.getY(), pos.getZ(), oldPower, newPower); ++ } ++ } + this.world.notifyNeighborsOfStateChange(this.pos, this.getBlockType(), false); + + if (this.getChestType() == BlockChest.Type.TRAP) +@@ -356,7 +393,7 @@ + @SuppressWarnings("unchecked") + @Override + @Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @Nullable EnumFacing facing) + { + if (capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + { diff --git a/patches/net/minecraft/tileentity/TileEntityCommandBlock.java.patch b/patches/net/minecraft/tileentity/TileEntityCommandBlock.java.patch new file mode 100644 index 00000000..ec8168ec --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityCommandBlock.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityCommandBlock.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityCommandBlock.java +@@ -25,6 +25,9 @@ + private boolean sendToClient; + private final CommandBlockBaseLogic commandBlockLogic = new CommandBlockBaseLogic() + { ++ { ++ sender = new org.bukkit.craftbukkit.command.CraftBlockCommandSender(this); ++ } + public BlockPos getPosition() + { + return TileEntityCommandBlock.this.pos; +@@ -134,7 +137,7 @@ + boolean flag = this.auto; + this.auto = autoIn; + +- if (!flag && autoIn && !this.powered && this.world != null && this.getMode() != TileEntityCommandBlock.Mode.SEQUENCE) ++ if (!flag && autoIn && !this.powered && this.world != null && this.getMode() != Mode.SEQUENCE) + { + Block block = this.getBlockType(); + diff --git a/patches/net/minecraft/tileentity/TileEntityDispenser.java.patch b/patches/net/minecraft/tileentity/TileEntityDispenser.java.patch new file mode 100644 index 00000000..6a39a1ec --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityDispenser.java.patch @@ -0,0 +1,57 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityDispenser.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityDispenser.java +@@ -1,5 +1,6 @@ + package net.minecraft.tileentity; + ++import java.util.List; + import java.util.Random; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.InventoryPlayer; +@@ -12,12 +13,38 @@ + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.FixTypes; + import net.minecraft.util.datafix.walkers.ItemStackDataLists; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + + public class TileEntityDispenser extends TileEntityLockableLoot + { + private static final Random RNG = new Random(); + private NonNullList stacks = NonNullList.withSize(9, ItemStack.EMPTY); + ++ private int maxStack = MAX_STACK; ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ ++ public List getContents() { ++ return this.stacks; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ + public int getSizeInventory() + { + return 9; +@@ -112,7 +139,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public String getGuiID() diff --git a/patches/net/minecraft/tileentity/TileEntityEndGateway.java.patch b/patches/net/minecraft/tileentity/TileEntityEndGateway.java.patch new file mode 100644 index 00000000..c43a391c --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityEndGateway.java.patch @@ -0,0 +1,54 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityEndGateway.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityEndGateway.java +@@ -5,6 +5,7 @@ + import javax.annotation.Nullable; + import net.minecraft.block.state.IBlockState; + import net.minecraft.entity.Entity; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTUtil; +@@ -24,14 +25,18 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.event.player.PlayerTeleportEvent; + + public class TileEntityEndGateway extends TileEntityEndPortal implements ITickable + { + private static final Logger LOGGER = LogManager.getLogger(); + private long age; + private int teleportCooldown; +- private BlockPos exitPortal; +- private boolean exactTeleport; ++ public BlockPos exitPortal; ++ public boolean exactTeleport; + + public NBTTagCompound writeToNBT(NBTTagCompound compound) + { +@@ -171,6 +176,22 @@ + if (this.exitPortal != null) + { + BlockPos blockpos = this.exactTeleport ? this.exitPortal : this.findExitPosition(); ++ if (entityIn instanceof EntityPlayerMP) { ++ org.bukkit.craftbukkit.entity.CraftPlayer player = (CraftPlayer) entityIn.getBukkitEntity(); ++ Location location = new Location(world.getWorld(), (double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D); ++ location.setPitch(player.getLocation().getPitch()); ++ location.setYaw(player.getLocation().getYaw()); ++ ++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.END_GATEWAY); ++ Bukkit.getPluginManager().callEvent(teleEvent); ++ if (teleEvent.isCancelled()) { ++ return; ++ } ++ ++ ((EntityPlayerMP) entityIn).connection.teleport(teleEvent.getTo()); ++ this.triggerCooldown(); // CraftBukkit - call at end of method ++ return; ++ } + entityIn.setPositionAndUpdate((double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 0.5D, (double)blockpos.getZ() + 0.5D); + } + diff --git a/patches/net/minecraft/tileentity/TileEntityFurnace.java.patch b/patches/net/minecraft/tileentity/TileEntityFurnace.java.patch new file mode 100644 index 00000000..56efff79 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityFurnace.java.patch @@ -0,0 +1,208 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityFurnace.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityFurnace.java +@@ -22,6 +22,7 @@ + import net.minecraft.item.ItemTool; + import net.minecraft.item.crafting.FurnaceRecipes; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.EnumFacing; + import net.minecraft.util.ITickable; + import net.minecraft.util.NonNullList; +@@ -31,7 +32,14 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.inventory.FurnaceBurnEvent; ++import org.bukkit.event.inventory.FurnaceSmeltEvent; + ++import java.util.List; ++ + public class TileEntityFurnace extends TileEntityLockable implements ITickable, ISidedInventory + { + private static final int[] SLOTS_TOP = new int[] {0}; +@@ -44,6 +52,30 @@ + private int totalCookTime; + private String furnaceCustomName; + ++ private int lastTick = MinecraftServer.currentTick; ++ private int maxStack = MAX_STACK; ++ public List transaction = new java.util.ArrayList(); ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ public List getContents() { ++ return this.furnaceItemStacks; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ + public int getSizeInventory() + { + return this.furnaceItemStacks.size(); +@@ -150,6 +182,7 @@ + + public int getInventoryStackLimit() + { ++ // TODO: Should it be maxStack? + return 64; + } + +@@ -166,12 +199,31 @@ + + public void update() + { +- boolean flag = this.isBurning(); ++ boolean flag = (this.getBlockType() == Blocks.LIT_FURNACE); // CraftBukkit - SPIGOT-844 - Check if furnace block is lit using the block instead of burn time + boolean flag1 = false; + ++ // CraftBukkit start - Use wall time instead of ticks for cooking ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ this.lastTick = MinecraftServer.currentTick; ++ ++ // CraftBukkit - moved from below - edited for wall time ++ if (this.isBurning() && this.canSmelt()) { ++ this.cookTime += elapsedTicks; ++ if (this.cookTime >= this.totalCookTime) { ++ this.cookTime -= this.totalCookTime; // Paper ++ this.totalCookTime = this.getCookTime((ItemStack) this.furnaceItemStacks.get(0)); ++ this.smeltItem(); ++ flag1 = true; ++ } ++ } else { ++ this.cookTime = 0; ++ } ++ // CraftBukkit end ++ + if (this.isBurning()) + { +- --this.furnaceBurnTime; ++ // --this.furnaceBurnTime; ++ this.furnaceBurnTime -= elapsedTicks; // CraftBukkit - use elapsedTicks in place of constant + } + + if (!this.world.isRemote) +@@ -180,13 +232,21 @@ + + if (this.isBurning() || !itemstack.isEmpty() && !((ItemStack)this.furnaceItemStacks.get(0)).isEmpty()) + { +- if (!this.isBurning() && this.canSmelt()) +- { +- this.furnaceBurnTime = getItemBurnTime(itemstack); +- this.currentItemBurnTime = this.furnaceBurnTime; ++ // CraftBukkit start - Handle multiple elapsed ticks ++ if (this.furnaceBurnTime <= 0 && this.canSmelt()) { // CraftBukkit - == to <= ++ CraftItemStack fuel = CraftItemStack.asCraftMirror(itemstack); + +- if (this.isBurning()) +- { ++ FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(this.world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), fuel, getItemBurnTime(itemstack)); ++ this.world.getServer().getPluginManager().callEvent(furnaceBurnEvent); ++ ++ if (furnaceBurnEvent.isCancelled()) { ++ return; ++ } ++ ++ this.currentItemBurnTime = furnaceBurnEvent.getBurnTime(); ++ this.furnaceBurnTime += this.currentItemBurnTime; ++ if (this.furnaceBurnTime > 0 && furnaceBurnEvent.isBurning()) { ++ // CraftBukkit end + flag1 = true; + + if (!itemstack.isEmpty()) +@@ -203,6 +263,7 @@ + } + } + ++ /* CraftBukkit start - Moved up + if (this.isBurning() && this.canSmelt()) + { + ++this.cookTime; +@@ -219,6 +280,7 @@ + { + this.cookTime = 0; + } ++ */ + } + else if (!this.isBurning() && this.cookTime > 0) + { +@@ -229,6 +291,7 @@ + { + flag1 = true; + BlockFurnace.setState(this.isBurning(), this.world, this.pos); ++ this.updateContainingBlockInfo(); // CraftBukkit - Invalidate tile entity's cached block type + } + } + +@@ -289,6 +352,30 @@ + ItemStack itemstack1 = FurnaceRecipes.instance().getSmeltingResult(itemstack); + ItemStack itemstack2 = this.furnaceItemStacks.get(2); + ++ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); ++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); ++ ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), source, result); ++ this.world.getServer().getPluginManager().callEvent(furnaceSmeltEvent); ++ ++ if (furnaceSmeltEvent.isCancelled()) { ++ return; ++ } ++ ++ result = furnaceSmeltEvent.getResult(); ++ itemstack1 = CraftItemStack.asNMSCopy(result); ++ ++ if (!itemstack1.isEmpty()) { ++ if (itemstack2.isEmpty()) { ++ this.furnaceItemStacks.set(2, itemstack1.copy()); ++ } else if (CraftItemStack.asCraftMirror(itemstack2).isSimilar(result)) { ++ itemstack2.grow(itemstack1.getCount()); ++ } else { ++ return; ++ } ++ } ++ ++ /* + if (itemstack2.isEmpty()) + { + this.furnaceItemStacks.set(2, itemstack1.copy()); +@@ -297,6 +384,7 @@ + { + itemstack2.grow(itemstack1.getCount()); + } ++ */ + + if (itemstack.getItem() == Item.getItemFromBlock(Blocks.SPONGE) && itemstack.getMetadata() == 1 && !((ItemStack)this.furnaceItemStacks.get(1)).isEmpty() && ((ItemStack)this.furnaceItemStacks.get(1)).getItem() == Items.BUCKET) + { +@@ -533,14 +621,14 @@ + this.furnaceItemStacks.clear(); + } + +- net.minecraftforge.items.IItemHandler handlerTop = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.UP); +- net.minecraftforge.items.IItemHandler handlerBottom = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.DOWN); +- net.minecraftforge.items.IItemHandler handlerSide = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, net.minecraft.util.EnumFacing.WEST); ++ net.minecraftforge.items.IItemHandler handlerTop = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.UP); ++ net.minecraftforge.items.IItemHandler handlerBottom = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.DOWN); ++ net.minecraftforge.items.IItemHandler handlerSide = new net.minecraftforge.items.wrapper.SidedInvWrapper(this, EnumFacing.WEST); + + @SuppressWarnings("unchecked") + @Override + @javax.annotation.Nullable +- public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @javax.annotation.Nullable net.minecraft.util.EnumFacing facing) ++ public T getCapability(net.minecraftforge.common.capabilities.Capability capability, @javax.annotation.Nullable EnumFacing facing) + { + if (facing != null && capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + if (facing == EnumFacing.DOWN) diff --git a/patches/net/minecraft/tileentity/TileEntityHopper.java.patch b/patches/net/minecraft/tileentity/TileEntityHopper.java.patch new file mode 100644 index 00000000..a6b8b38c --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityHopper.java.patch @@ -0,0 +1,200 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityHopper.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityHopper.java +@@ -7,12 +7,14 @@ + import net.minecraft.block.BlockHopper; + import net.minecraft.entity.Entity; + import net.minecraft.entity.item.EntityItem; ++import net.minecraft.entity.item.EntityMinecartHopper; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.InventoryPlayer; + import net.minecraft.inventory.Container; + import net.minecraft.inventory.ContainerHopper; + import net.minecraft.inventory.IInventory; + import net.minecraft.inventory.ISidedInventory; ++import net.minecraft.inventory.InventoryLargeChest; + import net.minecraft.inventory.ItemStackHelper; + import net.minecraft.item.ItemStack; + import net.minecraft.nbt.NBTTagCompound; +@@ -27,6 +29,12 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.event.inventory.InventoryPickupItemEvent; ++import org.bukkit.inventory.Inventory; + + public class TileEntityHopper extends TileEntityLockableLoot implements IHopper, ITickable + { +@@ -34,6 +42,32 @@ + private int transferCooldown = -1; + private long tickedGameTime; + ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.inventory; ++ } ++ ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return transaction; ++ } ++ + public static void registerFixesHopper(DataFixer fixer) + { + fixer.registerWalker(FixTypes.BLOCK_ENTITY, new ItemStackDataLists(TileEntityHopper.class, new String[] {"Items"})); +@@ -106,7 +140,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public void update() +@@ -144,7 +178,7 @@ + + if (flag) + { +- this.setTransferCooldown(8); ++ this.setTransferCooldown(world.spigotConfig.hopperTransfer); // Spigot + this.markDirty(); + return true; + } +@@ -213,11 +247,36 @@ + if (!this.getStackInSlot(i).isEmpty()) + { + ItemStack itemstack = this.getStackInSlot(i).copy(); +- ItemStack itemstack1 = putStackInInventoryAllSlots(this, iinventory, this.decrStackSize(i, 1), enumfacing); ++ // ItemStack itemstack1 = putStackInInventoryAllSlots(this, iinventory, this.decrStackSize(i, 1), enumfacing); ++ // CraftBukkit start - Call event when pushing items into other inventories ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.decrStackSize(i, 1)); + ++ Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof InventoryLargeChest) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); ++ } else { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); ++ this.getWorld().getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.setInventorySlotContents(i, itemstack); ++ this.setTransferCooldown(8); // Delay hopper checks ++ return false; ++ } ++ ItemStack itemstack1 = putStackInInventoryAllSlots(this, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumfacing); ++ + if (itemstack1.isEmpty()) + { +- iinventory.markDirty(); ++ // iinventory.markDirty(); ++ if (event.getItem().equals(oitemstack)) { ++ iinventory.markDirty(); ++ } else { ++ this.setInventorySlotContents(i, itemstack); ++ } ++ // CraftBukkit end + return true; + } + +@@ -358,14 +417,47 @@ + if (!itemstack.isEmpty() && canExtractItemFromSlot(inventoryIn, itemstack, index, direction)) + { + ItemStack itemstack1 = itemstack.copy(); +- ItemStack itemstack2 = putStackInInventoryAllSlots(inventoryIn, hopper, inventoryIn.decrStackSize(index, 1), (EnumFacing)null); ++ // ItemStack itemstack2 = putStackInInventoryAllSlots(inventoryIn, hopper, inventoryIn.decrStackSize(index, 1), (EnumFacing)null); ++ // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(inventoryIn.decrStackSize(index, hopper.getWorld().spigotConfig.hopperAmount)); + ++ Inventory sourceInventory; ++ // Have to special case large chests as they work oddly ++ if (inventoryIn instanceof InventoryLargeChest) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) inventoryIn); ++ } else { ++ sourceInventory = inventoryIn.getOwner().getInventory(); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), hopper.getOwner().getInventory(), false); ++ ++ hopper.getWorld().getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ inventoryIn.setInventorySlotContents(index, itemstack1); ++ ++ if (hopper instanceof TileEntityHopper) { ++ ((TileEntityHopper) hopper).setTransferCooldown(hopper.getWorld().spigotConfig.hopperTransfer); // Delay hopper checks ++ } else if (hopper instanceof EntityMinecartHopper) { ++ ((EntityMinecartHopper) hopper).setTransferTicker(hopper.getWorld().spigotConfig.hopperTransfer / 2); // Delay hopper minecart checks ++ } ++ ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemstack2 = putStackInInventoryAllSlots(inventoryIn, hopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ + if (itemstack2.isEmpty()) + { +- inventoryIn.markDirty(); ++ // inventoryIn.markDirty(); ++ if (event.getItem().equals(oitemstack)) { ++ inventoryIn.markDirty(); ++ } else { ++ inventoryIn.setInventorySlotContents(index, itemstack1); ++ } ++ // CraftBukkit end + return true; + } +- ++ itemstack1.shrink(origCount - itemstack2.getCount()); + inventoryIn.setInventorySlotContents(index, itemstack1); + } + +@@ -382,6 +474,11 @@ + } + else + { ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(destination.getOwner().getInventory(), (org.bukkit.entity.Item) entity.getBukkitEntity()); ++ entity.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } + ItemStack itemstack = entity.getItem().copy(); + ItemStack itemstack1 = putStackInInventoryAllSlots(source, destination, itemstack, (EnumFacing)null); + +@@ -491,7 +588,7 @@ + } + } + +- tileentityhopper1.setTransferCooldown(8 - k); ++ tileentityhopper1.setTransferCooldown(tileentityhopper1.world.spigotConfig.hopperTransfer - k); // Spigot + } + } + +@@ -525,6 +622,7 @@ + int j = MathHelper.floor(y); + int k = MathHelper.floor(z); + BlockPos blockpos = new BlockPos(i, j, k); ++ if (!worldIn.isBlockLoaded(blockpos)) return null; // Spigot + net.minecraft.block.state.IBlockState state = worldIn.getBlockState(blockpos); + Block block = state.getBlock(); + diff --git a/patches/net/minecraft/tileentity/TileEntityLockable.java.patch b/patches/net/minecraft/tileentity/TileEntityLockable.java.patch new file mode 100644 index 00000000..ee7a45d0 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityLockable.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityLockable.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityLockable.java +@@ -71,4 +71,10 @@ + { + return capability == net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY || super.hasCapability(capability, facing); + } ++ ++ @Override ++ public org.bukkit.Location getLocation() { ++ if (world == null) return null; ++ return new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); ++ } + } diff --git a/patches/net/minecraft/tileentity/TileEntityNote.java.patch b/patches/net/minecraft/tileentity/TileEntityNote.java.patch new file mode 100644 index 00000000..1c89e7ee --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityNote.java.patch @@ -0,0 +1,24 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityNote.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityNote.java +@@ -44,7 +44,7 @@ + { + IBlockState iblockstate = worldIn.getBlockState(posIn.down()); + Material material = iblockstate.getMaterial(); +- int i = 0; ++ byte i = 0; + + if (material == Material.ROCK) + { +@@ -93,7 +93,11 @@ + i = 9; + } + +- worldIn.addBlockEvent(posIn, Blocks.NOTEBLOCK, i, this.note); ++ // worldIn.addBlockEvent(posIn, Blocks.NOTEBLOCK, i, this.note); ++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(this.world, posIn.getX(), posIn.getY(), posIn.getZ(), i, this.note); ++ if (!event.isCancelled()) { ++ worldIn.addBlockEvent(posIn, Blocks.NOTEBLOCK, event.getInstrument().getType(), event.getNote().getId()); ++ } + } + } + } diff --git a/patches/net/minecraft/tileentity/TileEntityShulkerBox.java.patch b/patches/net/minecraft/tileentity/TileEntityShulkerBox.java.patch new file mode 100644 index 00000000..2b089444 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityShulkerBox.java.patch @@ -0,0 +1,52 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityShulkerBox.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityShulkerBox.java +@@ -29,6 +29,8 @@ + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; + + public class TileEntityShulkerBox extends TileEntityLockableLoot implements ITickable, ISidedInventory + { +@@ -199,7 +201,7 @@ + + public int getInventoryStackLimit() + { +- return 64; ++ return maxStack; + } + + public boolean receiveClientEvent(int id, int type) +@@ -365,6 +367,31 @@ + super.clear(); + } + ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public void onOpen(CraftHumanEntity who) { ++ transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ transaction.remove(who); ++ } ++ ++ @Override ++ public List getContents() { ++ return items; ++ } ++ ++ public List getViewers() { ++ return transaction; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ maxStack = size; ++ } ++ + public boolean isCleared() + { + return this.hasBeenCleared; diff --git a/patches/net/minecraft/tileentity/TileEntitySign.java.patch b/patches/net/minecraft/tileentity/TileEntitySign.java.patch new file mode 100644 index 00000000..623f9020 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntitySign.java.patch @@ -0,0 +1,120 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntitySign.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntitySign.java +@@ -24,9 +24,11 @@ + { + public final ITextComponent[] signText = new ITextComponent[] {new TextComponentString(""), new TextComponentString(""), new TextComponentString(""), new TextComponentString("")}; + public int lineBeingEdited = -1; +- private boolean isEditable = true; ++ public boolean isEditable = true; + private EntityPlayer player; + private final CommandResultStats stats = new CommandResultStats(); ++ private static final boolean CONVERT_LEGACY_SIGNS = Boolean.getBoolean("convertLegacySigns"); ++ public java.util.UUID signEditor; // Paper + + public NBTTagCompound writeToNBT(NBTTagCompound compound) + { +@@ -38,6 +40,10 @@ + compound.setString("Text" + (i + 1), s); + } + ++ if (CONVERT_LEGACY_SIGNS) { // Paper ++ compound.setBoolean("Bukkit.isConverted", true); ++ } ++ + this.stats.writeStatsToNBT(compound); + return compound; + } +@@ -61,6 +67,10 @@ + { + return permLevel <= 2; //Forge: Fixes MC-75630 - Exploit with signs and command blocks + } ++ public boolean canUseCommand(int permLevel, String commandName, String perm) ++ { ++ return permLevel <= 2; //Forge: Fixes MC-75630 - Exploit with signs and command blocks ++ } + public BlockPos getPosition() + { + return TileEntitySign.this.pos; +@@ -79,19 +89,38 @@ + } + }; + ++ // CraftBukkit start - Add an option to convert signs correctly ++ // This is done with a flag instead of all the time because ++ // we have no way to tell whether a sign is from 1.7.10 or 1.8 ++ ++ boolean oldSign = Boolean.getBoolean("convertLegacySigns") && !compound.getBoolean("Bukkit.isConverted"); ++ + for (int i = 0; i < 4; ++i) + { + String s = compound.getString("Text" + (i + 1)); +- ITextComponent itextcomponent = ITextComponent.Serializer.jsonToComponent(s); ++ // ITextComponent itextcomponent = ITextComponent.Serializer.jsonToComponent(s); ++ if (s != null && s.length() > 2048) { ++ s = "\"\""; ++ } + + try + { +- this.signText[i] = TextComponentUtils.processComponent(icommandsender, itextcomponent, (Entity)null); ++ ITextComponent ichatbasecomponent = ITextComponent.Serializer.jsonToComponent(s); ++ ++ if (oldSign) { ++ signText[i] = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s)[0]; ++ continue; ++ } ++ // CraftBukkit end ++ ++ try { ++ this.signText[i] = TextComponentUtils.processComponent(icommandsender, ichatbasecomponent, (Entity) null); ++ } catch (CommandException commandexception) { ++ this.signText[i] = ichatbasecomponent; ++ } ++ } catch (com.google.gson.JsonParseException jsonparseexception) { ++ this.signText[i] = new TextComponentString(s); + } +- catch (CommandException var7) +- { +- this.signText[i] = itextcomponent; +- } + } + + this.stats.readStatsFromNBT(compound); +@@ -131,7 +160,10 @@ + + public void setPlayer(EntityPlayer playerIn) + { +- this.player = playerIn; ++ // Paper start ++ //this.player = playerIn; ++ signEditor = playerIn != null ? playerIn.getUniqueID() : null; ++ // Paper end + } + + public EntityPlayer getPlayer() +@@ -158,6 +190,11 @@ + { + return permLevel <= 2; + } ++ ++ public boolean canUseCommand(int permLevel, String commandName, String perm) ++ { ++ return permLevel <= 2; ++ } + public BlockPos getPosition() + { + return TileEntitySign.this.pos; +@@ -201,7 +238,12 @@ + + if (clickevent.getAction() == ClickEvent.Action.RUN_COMMAND) + { +- playerIn.getServer().getCommandManager().executeCommand(icommandsender, clickevent.getValue()); ++ // playerIn.getServer().getCommandManager().executeCommand(icommandsender, clickevent.getValue()); ++ CommandBlockBaseLogic.executeSafely(icommandsender, new org.bukkit.craftbukkit.command.ProxiedNativeCommandSender( ++ icommandsender, ++ new org.bukkit.craftbukkit.command.CraftBlockCommandSender(icommandsender), ++ playerIn.getBukkitEntity() ++ ), clickevent.getValue()); + } + } + } diff --git a/patches/net/minecraft/tileentity/TileEntitySkull.java.patch b/patches/net/minecraft/tileentity/TileEntitySkull.java.patch new file mode 100644 index 00000000..34e93900 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntitySkull.java.patch @@ -0,0 +1,212 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntitySkull.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntitySkull.java +@@ -1,34 +1,92 @@ + package net.minecraft.tileentity; + ++import com.google.common.base.Predicate; ++import com.google.common.cache.CacheBuilder; ++import com.google.common.cache.CacheLoader; ++import com.google.common.cache.LoadingCache; + import com.google.common.collect.Iterables; ++import com.google.common.util.concurrent.Futures; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import com.mojang.authlib.Agent; + import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.ProfileLookupCallback; + import com.mojang.authlib.minecraft.MinecraftSessionService; + import com.mojang.authlib.properties.Property; + import java.util.UUID; ++import java.util.concurrent.*; + import javax.annotation.Nullable; + import net.minecraft.block.BlockSkull; ++import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTUtil; + import net.minecraft.network.play.server.SPacketUpdateTileEntity; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.management.PlayerProfileCache; +-import net.minecraft.util.EnumFacing; +-import net.minecraft.util.ITickable; +-import net.minecraft.util.Mirror; +-import net.minecraft.util.Rotation; +-import net.minecraft.util.StringUtils; ++import net.minecraft.util.*; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; +- + public class TileEntitySkull extends TileEntity implements ITickable + { + private int skullType; +- private int skullRotation; ++ public int skullRotation; + private GameProfile playerProfile; + private int dragonAnimatedTicks; + private boolean dragonAnimated; + private static PlayerProfileCache profileCache; + private static MinecraftSessionService sessionService; ++ // Spigot start ++ public static final ExecutorService executor = Executors.newFixedThreadPool(3, ++ new ThreadFactoryBuilder() ++ .setNameFormat("Head Conversion Thread - %1$d") ++ .build() ++ ); ++ public static final LoadingCache skinCache = CacheBuilder.newBuilder() ++ .maximumSize( 5000 ) ++ .expireAfterAccess( 60, TimeUnit.MINUTES ) ++ .build( new CacheLoader() ++ { ++ @Override ++ public GameProfile load(String key) throws Exception ++ { ++ final GameProfile[] profiles = new GameProfile[1]; ++ ProfileLookupCallback gameProfileLookup = new ProfileLookupCallback() { + ++ @Override ++ public void onProfileLookupSucceeded(GameProfile gp) { ++ profiles[0] = gp; ++ } ++ ++ @Override ++ public void onProfileLookupFailed(GameProfile gp, Exception excptn) { ++ profiles[0] = gp; ++ } ++ }; ++ ++ MinecraftServer.getServerCB().getGameProfileRepository().findProfilesByNames(new String[] { key }, Agent.MINECRAFT, gameProfileLookup); ++ ++ GameProfile profile = profiles[ 0 ]; ++ if (profile == null) { ++ UUID uuid = EntityPlayer.getUUID(new GameProfile(null, key)); ++ profile = new GameProfile(uuid, key); ++ ++ gameProfileLookup.onProfileLookupSucceeded(profile); ++ } else ++ { ++ ++ Property property = Iterables.getFirst( profile.getProperties().get( "textures" ), null ); ++ ++ if ( property == null ) ++ { ++ profile = MinecraftServer.getServerCB().getMinecraftSessionService().fillProfileProperties( profile, true ); ++ } ++ } ++ ++ ++ return profile; ++ } ++ } ); ++ // Spigot end ++ + public static void setProfileCache(PlayerProfileCache profileCacheIn) + { + profileCache = profileCacheIn; +@@ -134,47 +192,88 @@ + + private void updatePlayerProfile() + { +- this.playerProfile = updateGameprofile(this.playerProfile); +- this.markDirty(); ++ // Spigot start ++ GameProfile profile = this.playerProfile; ++ setType( 0 ); // Work around client bug ++ updateGameprofile(profile, new Predicate() { ++ ++ @Override ++ public boolean apply(GameProfile input) { ++ setType(3); // Work around client bug ++ playerProfile = input; ++ markDirty(); ++ if (world != null) { ++ world.notifyLightSet(pos); // PAIL: notify ++ } ++ return false; ++ } ++ }, false); ++ // Spigot end ++ + } + +- public static GameProfile updateGameprofile(GameProfile input) ++ // Spigot start - Support async lookups ++ public static Future updateGameprofile(final GameProfile input, final Predicate callback, boolean sync) + { + if (input != null && !StringUtils.isNullOrEmpty(input.getName())) + { + if (input.isComplete() && input.getProperties().containsKey("textures")) + { +- return input; +- } +- else if (profileCache != null && sessionService != null) +- { +- GameProfile gameprofile = profileCache.getGameProfileForUsername(input.getName()); ++ callback.apply(input); ++ } else if (MinecraftServer.getServerCB() == null) { ++ callback.apply(input); ++ } else { ++ GameProfile profile = skinCache.getIfPresent(input.getName().toLowerCase(java.util.Locale.ROOT)); ++ if (profile != null && Iterables.getFirst(profile.getProperties().get("textures"), (Object) null) != null) { ++ callback.apply(profile); + +- if (gameprofile == null) +- { +- return input; ++ return Futures.immediateFuture(profile); + } + else + { +- Property property = (Property)Iterables.getFirst(gameprofile.getProperties().get("textures"), (Object)null); +- +- if (property == null) +- { +- gameprofile = sessionService.fillProfileProperties(gameprofile, true); ++ Callable callable = new Callable() { ++ @Override ++ public GameProfile call() { ++ final GameProfile profile = skinCache.getUnchecked(input.getName().toLowerCase(java.util.Locale.ROOT)); ++ MinecraftServer.getServerCB().processQueue.add(new Runnable() { ++ @Override ++ public void run() { ++ if (profile == null) { ++ callback.apply(input); ++ } else { ++ callback.apply(profile); ++ } ++ } ++ }); ++ return profile; ++ } ++ }; ++ if (sync) { ++ try { ++ return Futures.immediateFuture(callable.call()); ++ } catch (Exception ex) { ++ com.google.common.base.Throwables.throwIfUnchecked(ex); ++ throw new RuntimeException(ex); // Not possible ++ } ++ } else { ++ return executor.submit(callable); + } + +- return gameprofile; + } ++ } + } + else + { +- return input; ++ callback.apply(input); + } ++ ++ return Futures.immediateFuture(input); + } +- else ++ // Spigot end ++ ++ public static GameProfile updateGameprofile(GameProfile input) + { +- return input; +- } ++ return com.google.common.util.concurrent.Futures.getUnchecked(updateGameprofile(input, com.google.common.base.Predicates.alwaysTrue(), true)); // Kettle + } + + public int getSkullType() diff --git a/patches/net/minecraft/tileentity/TileEntityStructure.java.patch b/patches/net/minecraft/tileentity/TileEntityStructure.java.patch new file mode 100644 index 00000000..2dc26b64 --- /dev/null +++ b/patches/net/minecraft/tileentity/TileEntityStructure.java.patch @@ -0,0 +1,37 @@ +--- ../src-base/minecraft/net/minecraft/tileentity/TileEntityStructure.java ++++ ../src-work/minecraft/net/minecraft/tileentity/TileEntityStructure.java +@@ -36,20 +36,20 @@ + + public class TileEntityStructure extends TileEntity + { +- private String name = ""; +- private String author = ""; +- private String metadata = ""; +- private BlockPos position = new BlockPos(0, 1, 0); +- private BlockPos size = BlockPos.ORIGIN; +- private Mirror mirror = Mirror.NONE; +- private Rotation rotation = Rotation.NONE; +- private TileEntityStructure.Mode mode = TileEntityStructure.Mode.DATA; +- private boolean ignoreEntities = true; +- private boolean powered; +- private boolean showAir; +- private boolean showBoundingBox = true; +- private float integrity = 1.0F; +- private long seed; ++ public String name = ""; ++ public String author = ""; ++ public String metadata = ""; ++ public BlockPos position = new BlockPos(0, 1, 0); ++ public BlockPos size = BlockPos.ORIGIN; ++ public Mirror mirror = Mirror.NONE; ++ public Rotation rotation = Rotation.NONE; ++ public Mode mode = Mode.DATA; ++ public boolean ignoreEntities = true; ++ public boolean powered; ++ public boolean showAir; ++ public boolean showBoundingBox = true; ++ public float integrity = 1.0F; ++ public long seed; + + public NBTTagCompound writeToNBT(NBTTagCompound compound) + { diff --git a/patches/net/minecraft/util/CooldownTracker.java.patch b/patches/net/minecraft/util/CooldownTracker.java.patch new file mode 100644 index 00000000..29735fd5 --- /dev/null +++ b/patches/net/minecraft/util/CooldownTracker.java.patch @@ -0,0 +1,27 @@ +--- ../src-base/minecraft/net/minecraft/util/CooldownTracker.java ++++ ../src-work/minecraft/net/minecraft/util/CooldownTracker.java +@@ -11,8 +11,8 @@ + + public class CooldownTracker + { +- private final Map cooldowns = Maps.newHashMap(); +- private int ticks; ++ public final Map cooldowns = Maps.newHashMap(); ++ public int ticks; + + public boolean hasCooldown(Item itemIn) + { +@@ -77,10 +77,10 @@ + { + } + +- class Cooldown ++ public class Cooldown + { +- final int createTicks; +- final int expireTicks; ++ public final int createTicks; ++ public final int expireTicks; + + private Cooldown(int createTicksIn, int expireTicksIn) + { diff --git a/patches/net/minecraft/util/DamageSource.java.patch b/patches/net/minecraft/util/DamageSource.java.patch new file mode 100644 index 00000000..5b38955a --- /dev/null +++ b/patches/net/minecraft/util/DamageSource.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/util/DamageSource.java ++++ ../src-work/minecraft/net/minecraft/util/DamageSource.java +@@ -45,6 +45,17 @@ + private boolean explosion; + public String damageType; + ++ private boolean sweep; ++ ++ public boolean isSweep() { ++ return sweep; ++ } ++ ++ public DamageSource sweep() { ++ this.sweep = true; ++ return this; ++ } ++ + public static DamageSource causeMobDamage(EntityLivingBase mob) + { + return new EntityDamageSource("mob", mob); diff --git a/patches/net/minecraft/util/EntityDamageSourceIndirect.java.patch b/patches/net/minecraft/util/EntityDamageSourceIndirect.java.patch new file mode 100644 index 00000000..1176ced6 --- /dev/null +++ b/patches/net/minecraft/util/EntityDamageSourceIndirect.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/util/EntityDamageSourceIndirect.java ++++ ../src-work/minecraft/net/minecraft/util/EntityDamageSourceIndirect.java +@@ -38,4 +38,9 @@ + String s1 = s + ".item"; + return !itemstack.isEmpty() && itemstack.hasDisplayName() && I18n.canTranslate(s1) ? new TextComponentTranslation(s1, new Object[] {entityLivingBaseIn.getDisplayName(), itextcomponent, itemstack.getTextComponent()}) : new TextComponentTranslation(s, new Object[] {entityLivingBaseIn.getDisplayName(), itextcomponent}); + } ++ ++ @Nullable ++ public Entity getProximateDamageSource() { ++ return super.getTrueSource(); ++ } + } diff --git a/patches/net/minecraft/util/EntitySelectors.java.patch b/patches/net/minecraft/util/EntitySelectors.java.patch new file mode 100644 index 00000000..94c9336a --- /dev/null +++ b/patches/net/minecraft/util/EntitySelectors.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/util/EntitySelectors.java ++++ ../src-work/minecraft/net/minecraft/util/EntitySelectors.java +@@ -161,7 +161,7 @@ + } + else if (entitylivingbase instanceof EntityLiving) + { +- return ((EntityLiving)entitylivingbase).canPickUpLoot(); ++ return ((EntityLiving)entitylivingbase).thisisatestof(); + } + else if (entitylivingbase instanceof EntityArmorStand) + { diff --git a/patches/net/minecraft/util/FoodStats.java.patch b/patches/net/minecraft/util/FoodStats.java.patch new file mode 100644 index 00000000..d8d646fa --- /dev/null +++ b/patches/net/minecraft/util/FoodStats.java.patch @@ -0,0 +1,104 @@ +--- ../src-base/minecraft/net/minecraft/util/FoodStats.java ++++ ../src-work/minecraft/net/minecraft/util/FoodStats.java +@@ -1,21 +1,30 @@ + package net.minecraft.util; + + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.item.ItemFood; + import net.minecraft.item.ItemStack; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.play.server.SPacketUpdateHealth; + import net.minecraft.world.EnumDifficulty; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.FoodLevelChangeEvent; + + public class FoodStats + { +- private int foodLevel = 20; +- private float foodSaturationLevel = 5.0F; +- private float foodExhaustionLevel; +- private int foodTimer; +- private int prevFoodLevel = 20; ++ public int foodLevel = 20; ++ public float foodSaturationLevel = 5.0F; ++ public float foodExhaustionLevel; ++ public int foodTimer; ++ public int prevFoodLevel = 20; + ++ public EntityPlayer player; // CraftBukkit // Kettle private - public entityplayer - player Fix mcp duplicate naming causes AppleCore to recognize errors ++ ++ public FoodStats() { ++ } ++ + public void addStats(int foodLevelIn, float foodSaturationModifier) + { + this.foodLevel = Math.min(foodLevelIn + this.foodLevel, 20); +@@ -24,7 +33,22 @@ + + public void addStats(ItemFood foodItem, ItemStack stack) + { +- this.addStats(foodItem.getHealAmount(stack), foodItem.getSaturationModifier(stack)); ++ // CraftBukkit start ++ if (player == null) // Fix NPE ++ { ++ this.addStats(foodItem.getHealAmount(stack), foodItem.getSaturationModifier(stack)); ++ } else { ++ int oldFoodLevel = foodLevel; ++ ++ FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(player, foodItem.getHealAmount(stack) + oldFoodLevel); ++ ++ if (!event.isCancelled()) { ++ this.addStats(event.getFoodLevel() - oldFoodLevel, foodItem.getSaturationModifier(stack)); ++ } ++ ++ ((EntityPlayerMP) player).getBukkitEntity().sendHealthUpdate(); ++ // CraftBukkit end ++ } + } + + public void onUpdate(EntityPlayer player) +@@ -42,7 +66,20 @@ + } + else if (enumdifficulty != EnumDifficulty.PEACEFUL) + { +- this.foodLevel = Math.max(this.foodLevel - 1, 0); ++ // CraftBukkit start ++ if (player == null) // Fix NPE ++ { ++ this.foodLevel = Math.max(this.foodLevel - 1, 0); ++ } else { ++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, Math.max(this.foodLevel - 1, 0)); ++ ++ if (!event.isCancelled()) { ++ this.foodLevel = event.getFoodLevel(); ++ } ++ ++ ((EntityPlayerMP) player).connection.sendPacket(new SPacketUpdateHealth(((EntityPlayerMP) player).getBukkitEntity().getScaledHealth(), this.foodLevel, this.foodSaturationLevel)); ++ // CraftBukkit end ++ } + } + } + +@@ -55,7 +92,7 @@ + if (this.foodTimer >= 10) + { + float f = Math.min(this.foodSaturationLevel, 6.0F); +- player.heal(f / 6.0F); ++ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); + this.addExhaustion(f); + this.foodTimer = 0; + } +@@ -66,8 +103,8 @@ + + if (this.foodTimer >= 80) + { +- player.heal(1.0F); +- this.addExhaustion(6.0F); ++ player.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason ++ this.addExhaustion((player == null) ? 6.0F : player.world.spigotConfig.regenExhaustion); // Spigot - Change to use configurable value + this.foodTimer = 0; + } + } diff --git a/patches/net/minecraft/util/datafix/walkers/BlockEntityTag.java.patch b/patches/net/minecraft/util/datafix/walkers/BlockEntityTag.java.patch new file mode 100644 index 00000000..f96ae776 --- /dev/null +++ b/patches/net/minecraft/util/datafix/walkers/BlockEntityTag.java.patch @@ -0,0 +1,12 @@ +--- ../src-base/minecraft/net/minecraft/util/datafix/walkers/BlockEntityTag.java ++++ ../src-work/minecraft/net/minecraft/util/datafix/walkers/BlockEntityTag.java +@@ -42,7 +42,8 @@ + + if (s1 == null) + { +- LOGGER.warn("Unable to resolve BlockEntity for ItemInstance: {}", (Object)s); ++ // CraftBukkit - Remove unnecessary warning (occurs when deserializing a Shulker Box item) ++ // LOGGER.warn("Unable to resolve BlockEntity for ItemInstance: {}", (Object)s); + flag = false; + } + else diff --git a/patches/net/minecraft/util/math/Vec3i.java.patch b/patches/net/minecraft/util/math/Vec3i.java.patch new file mode 100644 index 00000000..1b828cdb --- /dev/null +++ b/patches/net/minecraft/util/math/Vec3i.java.patch @@ -0,0 +1,22 @@ +--- ../src-base/minecraft/net/minecraft/util/math/Vec3i.java ++++ ../src-work/minecraft/net/minecraft/util/math/Vec3i.java +@@ -7,10 +7,19 @@ + public class Vec3i implements Comparable + { + public static final Vec3i NULL_VECTOR = new Vec3i(0, 0, 0); ++ // Paper start - Make mutable and protected for MutableBlockPos and PooledBlockPos + private final int x; + private final int y; + private final int z; + ++ public final boolean isValidLocation() { ++ return x >= -30000000 && z >= -30000000 && x < 30000000 && z < 30000000 && y >= 0 && y < 256; ++ } ++ public boolean isInvalidYLocation() { ++ return y < 0 || y >= 256; ++ } ++ // Paper end ++ + public Vec3i(int xIn, int yIn, int zIn) + { + this.x = xIn; diff --git a/patches/net/minecraft/util/text/Style.java.patch b/patches/net/minecraft/util/text/Style.java.patch new file mode 100644 index 00000000..ce060a5d --- /dev/null +++ b/patches/net/minecraft/util/text/Style.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraft/util/text/Style.java ++++ ../src-work/minecraft/net/minecraft/util/text/Style.java +@@ -361,15 +361,17 @@ + + public int hashCode() + { +- int i = this.color.hashCode(); +- i = 31 * i + this.bold.hashCode(); +- i = 31 * i + this.italic.hashCode(); +- i = 31 * i + this.underlined.hashCode(); +- i = 31 * i + this.strikethrough.hashCode(); +- i = 31 * i + this.obfuscated.hashCode(); +- i = 31 * i + this.clickEvent.hashCode(); +- i = 31 * i + this.hoverEvent.hashCode(); +- i = 31 * i + this.insertion.hashCode(); ++ // CraftBukkit start - fix npe ++ int i = color == null ? 0 : this.color.hashCode(); ++ i = 31 * i + (bold == null ? 0 : this.bold.hashCode()); ++ i = 31 * i + (italic == null ? 0 : this.italic.hashCode()); ++ i = 31 * i + (underlined == null ? 0 : this.underlined.hashCode()); ++ i = 31 * i + (strikethrough == null ? 0 : this.strikethrough.hashCode()); ++ i = 31 * i + (obfuscated == null ? 0 : this.obfuscated.hashCode()); ++ i = 31 * i + (clickEvent == null ? 0 : this.clickEvent.hashCode()); ++ i = 31 * i + (hoverEvent == null ? 0 : this.hoverEvent.hashCode()); ++ i = 31 * i + (insertion == null ? 0 : this.insertion.hashCode()); ++ // CraftBukkit end + return i; + } + diff --git a/patches/net/minecraft/util/text/TextFormatting.java.patch b/patches/net/minecraft/util/text/TextFormatting.java.patch new file mode 100644 index 00000000..7e95ef05 --- /dev/null +++ b/patches/net/minecraft/util/text/TextFormatting.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/util/text/TextFormatting.java ++++ ../src-work/minecraft/net/minecraft/util/text/TextFormatting.java +@@ -37,7 +37,7 @@ + private static final Map NAME_MAPPING = Maps.newHashMap(); + private static final Pattern FORMATTING_CODE_PATTERN = Pattern.compile("(?i)\u00a7[0-9A-FK-OR]"); + private final String name; +- private final char formattingCode; ++ public final char formattingCode; + private final boolean fancyStyling; + private final String controlString; + private final int colorIndex; diff --git a/patches/net/minecraft/village/MerchantRecipe.java.patch b/patches/net/minecraft/village/MerchantRecipe.java.patch new file mode 100644 index 00000000..8ad9c640 --- /dev/null +++ b/patches/net/minecraft/village/MerchantRecipe.java.patch @@ -0,0 +1,37 @@ +--- ../src-base/minecraft/net/minecraft/village/MerchantRecipe.java ++++ ../src-work/minecraft/net/minecraft/village/MerchantRecipe.java +@@ -5,16 +5,28 @@ + import net.minecraft.nbt.NBTTagCompound; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; + + public class MerchantRecipe + { +- private ItemStack itemToBuy; +- private ItemStack secondItemToBuy; +- private ItemStack itemToSell; +- private int toolUses; +- private int maxTradeUses; +- private boolean rewardsExp; ++ public ItemStack itemToBuy; ++ public ItemStack secondItemToBuy; ++ public ItemStack itemToSell; ++ public int toolUses; ++ public int maxTradeUses; ++ public boolean rewardsExp; + ++ private CraftMerchantRecipe bukkitHandle; ++ ++ public CraftMerchantRecipe asBukkit() { ++ return (bukkitHandle == null) ? bukkitHandle = new CraftMerchantRecipe(this) : bukkitHandle; ++ } ++ ++ public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, CraftMerchantRecipe bukkit) { ++ this(itemstack, itemstack1, itemstack2, i, j); ++ this.bukkitHandle = bukkit; ++ } ++ + public MerchantRecipe(NBTTagCompound tagCompound) + { + this.itemToBuy = ItemStack.EMPTY; diff --git a/patches/net/minecraft/village/Village.java.patch b/patches/net/minecraft/village/Village.java.patch new file mode 100644 index 00000000..565f5f4b --- /dev/null +++ b/patches/net/minecraft/village/Village.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/village/Village.java ++++ ../src-work/minecraft/net/minecraft/village/Village.java +@@ -83,7 +83,7 @@ + { + EntityIronGolem entityirongolem = new EntityIronGolem(this.world); + entityirongolem.setPosition(vec3d.x, vec3d.y, vec3d.z); +- this.world.spawnEntity(entityirongolem); ++ this.world.spawnEntity(entityirongolem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE); + ++this.numIronGolems; + } + } diff --git a/patches/net/minecraft/village/VillageSiege.java.patch b/patches/net/minecraft/village/VillageSiege.java.patch new file mode 100644 index 00000000..e243f4c5 --- /dev/null +++ b/patches/net/minecraft/village/VillageSiege.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/village/VillageSiege.java ++++ ../src-work/minecraft/net/minecraft/village/VillageSiege.java +@@ -182,7 +182,7 @@ + } + + entityzombie.setLocationAndAngles(vec3d.x, vec3d.y, vec3d.z, this.world.rand.nextFloat() * 360.0F, 0.0F); +- this.world.spawnEntity(entityzombie); ++ this.world.spawnEntity(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); + BlockPos blockpos = this.village.getCenter(); + entityzombie.setHomePosAndDistance(blockpos, this.village.getVillageRadius()); + return true; diff --git a/patches/net/minecraft/world/BossInfo.java.patch b/patches/net/minecraft/world/BossInfo.java.patch new file mode 100644 index 00000000..200a08e4 --- /dev/null +++ b/patches/net/minecraft/world/BossInfo.java.patch @@ -0,0 +1,17 @@ +--- ../src-base/minecraft/net/minecraft/world/BossInfo.java ++++ ../src-work/minecraft/net/minecraft/world/BossInfo.java +@@ -6,10 +6,10 @@ + public abstract class BossInfo + { + private final UUID uniqueId; +- protected ITextComponent name; +- protected float percent; +- protected BossInfo.Color color; +- protected BossInfo.Overlay overlay; ++ public ITextComponent name; ++ public float percent; ++ public Color color; ++ public Overlay overlay; + protected boolean darkenSky; + protected boolean playEndBossMusic; + protected boolean createFog; diff --git a/patches/net/minecraft/world/BossInfoServer.java.patch b/patches/net/minecraft/world/BossInfoServer.java.patch new file mode 100644 index 00000000..1506430e --- /dev/null +++ b/patches/net/minecraft/world/BossInfoServer.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/world/BossInfoServer.java ++++ ../src-work/minecraft/net/minecraft/world/BossInfoServer.java +@@ -14,7 +14,7 @@ + { + private final Set players = Sets.newHashSet(); + private final Set readOnlyPlayers; +- private boolean visible; ++ public boolean visible; + + public BossInfoServer(ITextComponent nameIn, BossInfo.Color colorIn, BossInfo.Overlay overlayIn) + { +@@ -92,7 +92,7 @@ + } + } + +- private void sendUpdate(SPacketUpdateBossInfo.Operation operationIn) ++ public void sendUpdate(SPacketUpdateBossInfo.Operation operationIn) + { + if (this.visible) + { diff --git a/patches/net/minecraft/world/Explosion.java.patch b/patches/net/minecraft/world/Explosion.java.patch new file mode 100644 index 00000000..4330a012 --- /dev/null +++ b/patches/net/minecraft/world/Explosion.java.patch @@ -0,0 +1,255 @@ +--- ../src-base/minecraft/net/minecraft/world/Explosion.java ++++ ../src-work/minecraft/net/minecraft/world/Explosion.java +@@ -14,8 +14,10 @@ + import net.minecraft.enchantment.EnchantmentProtection; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityLivingBase; ++import net.minecraft.entity.item.EntityFallingBlock; + import net.minecraft.entity.item.EntityTNTPrimed; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.projectile.EntityFireball; + import net.minecraft.init.Blocks; + import net.minecraft.init.SoundEvents; + import net.minecraft.util.DamageSource; +@@ -27,6 +29,10 @@ + import net.minecraft.util.math.Vec3d; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.block.BlockExplodeEvent; ++import org.bukkit.event.entity.EntityExplodeEvent; + + public class Explosion + { +@@ -37,12 +43,14 @@ + private final double x; + private final double y; + private final double z; +- private final Entity exploder; ++ public final Entity exploder; + private final float size; + private final List affectedBlockPositions; + private final Map playerKnockbackMap; + private final Vec3d position; + ++ public boolean wasCanceled = false; ++ + @SideOnly(Side.CLIENT) + public Explosion(World worldIn, Entity entityIn, double x, double y, double z, float size, List affectedPositions) + { +@@ -63,7 +71,8 @@ + this.playerKnockbackMap = Maps.newHashMap(); + this.world = worldIn; + this.exploder = entityIn; +- this.size = size; ++ // this.size = size; ++ this.size = (float) Math.max(size, 0.0); // CraftBukkit - clamp bad values + this.x = x; + this.y = y; + this.z = z; +@@ -74,6 +83,9 @@ + + public void doExplosionA() + { ++ if (this.size < 0.1F) { ++ return; ++ } + Set set = Sets.newHashSet(); + int i = 16; + +@@ -108,7 +120,7 @@ + f -= (f2 + 0.3F) * 0.3F; + } + +- if (f > 0.0F && (this.exploder == null || this.exploder.canExplosionDestroyBlock(this, this.world, blockpos, iblockstate, f))) ++ if (f > 0.0F && (this.exploder == null || this.exploder.canExplosionDestroyBlock(this, this.world, blockpos, iblockstate, f)) && blockpos.getY() < 256 && blockpos.getY() >= 0) // CraftBukkit - don't wrap explosions + { + set.add(blockpos); + } +@@ -154,9 +166,16 @@ + d5 = d5 / d13; + d7 = d7 / d13; + d9 = d9 / d13; +- double d14 = (double)this.world.getBlockDensity(vec3d, entity.getEntityBoundingBox()); ++ double d14 = this.getBlockDensity(vec3d, entity.getEntityBoundingBox()); // Paper - Optimize explosions + double d10 = (1.0D - d12) * d14; +- entity.attackEntityFrom(DamageSource.causeExplosionDamage(this), (float)((int)((d10 * d10 + d10) / 2.0D * 7.0D * (double)f3 + 1.0D))); ++ // entity.attackEntityFrom(DamageSource.causeExplosionDamage(this), (float)((int)((d10 * d10 + d10) / 2.0D * 7.0D * (double)f3 + 1.0D))); ++ CraftEventFactory.entityDamage = exploder; ++ entity.forceExplosionKnockback = false; ++ boolean wasDamaged = entity.attackEntityFrom(DamageSource.causeExplosionDamage(this), (float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D))); ++ CraftEventFactory.entityDamage = null; ++ if (!wasDamaged && !(entity instanceof EntityTNTPrimed || entity instanceof EntityFallingBlock) && !entity.forceExplosionKnockback) { ++ continue; ++ } + double d11 = d10; + + if (entity instanceof EntityLivingBase) +@@ -198,6 +217,48 @@ + + if (this.damagesTerrain) + { ++ org.bukkit.World bworld = this.world.getWorld(); ++ org.bukkit.entity.Entity explode = this.exploder == null ? null : this.exploder.getBukkitEntity(); ++ Location location = new Location(bworld, this.x, this.y, this.z); ++ ++ List blockList = Lists.newArrayList(); ++ for (int i1 = this.affectedBlockPositions.size() - 1; i1 >= 0; i1--) { ++ BlockPos cpos = (BlockPos) this.affectedBlockPositions.get(i1); ++ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ()); ++ if (bblock.getType() != org.bukkit.Material.AIR) { ++ blockList.add(bblock); ++ } ++ } ++ ++ boolean cancelled; ++ List bukkitBlocks; ++ float yield; ++ ++ if (explode != null) { ++ EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, 1.0F / this.size); ++ this.world.getServer().getPluginManager().callEvent(event); ++ cancelled = event.isCancelled(); ++ bukkitBlocks = event.blockList(); ++ yield = event.getYield(); ++ } else { ++ BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, 1.0F / this.size); ++ this.world.getServer().getPluginManager().callEvent(event); ++ cancelled = event.isCancelled(); ++ bukkitBlocks = event.blockList(); ++ yield = event.getYield(); ++ } ++ ++ this.affectedBlockPositions.clear(); ++ ++ for (org.bukkit.block.Block bblock : bukkitBlocks) { ++ BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ()); ++ affectedBlockPositions.add(coords); ++ } ++ ++ if (cancelled) { ++ this.wasCanceled = true; ++ return; ++ } + for (BlockPos blockpos : this.affectedBlockPositions) + { + IBlockState iblockstate = this.world.getBlockState(blockpos); +@@ -228,7 +289,9 @@ + { + if (block.canDropFromExplosion(this)) + { +- block.dropBlockAsItemWithChance(this.world, blockpos, this.world.getBlockState(blockpos), 1.0F / this.size, 0); ++ // CraftBukkit - add yield ++ // block.dropBlockAsItemWithChance(this.world, blockpos, this.world.getBlockState(blockpos), 1.0F / this.size, 0); ++ block.dropBlockAsItemWithChance(this.world, blockpos, this.world.getBlockState(blockpos), yield, 0); + } + + block.onBlockExploded(this.world, blockpos, this); +@@ -242,7 +305,12 @@ + { + if (this.world.getBlockState(blockpos1).getMaterial() == Material.AIR && this.world.getBlockState(blockpos1.down()).isFullBlock() && this.random.nextInt(3) == 0) + { +- this.world.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ // this.world.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ // CraftBukkit start - Ignition by explosion ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.world, blockpos1.getX(), blockpos1.getY(), blockpos1.getZ(), this).isCancelled()) { ++ this.world.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); ++ } ++ // CraftBukkit end + } + } + } +@@ -266,7 +334,8 @@ + } + else + { +- return this.exploder instanceof EntityLivingBase ? (EntityLivingBase)this.exploder : null; ++ // CraftBukkit start - obtain Fireball shooter for explosion tracking ++ return this.exploder instanceof EntityLivingBase ? (EntityLivingBase)this.exploder : (this.exploder instanceof EntityFireball ? ((EntityFireball) this.exploder).shootingEntity : null); + } + } + +@@ -281,4 +350,82 @@ + } + + public Vec3d getPosition(){ return this.position; } ++ ++ // Paper start - Optimize explosions ++ private float getBlockDensity(Vec3d vec3d, AxisAlignedBB aabb) { ++ CacheKey key = new CacheKey(this, aabb); ++ Float blockDensity = this.world.explosionDensityCache.get(key); ++ if (blockDensity == null) { ++ blockDensity = this.world.getBlockDensity(vec3d, aabb); ++ this.world.explosionDensityCache.put(key, blockDensity); ++ } ++ ++ return blockDensity; ++ } ++ ++ static class CacheKey { ++ private final World world; ++ private final double posX, posY, posZ; ++ private final double minX, minY, minZ; ++ private final double maxX, maxY, maxZ; ++ ++ public CacheKey(Explosion explosion, AxisAlignedBB aabb) { ++ this.world = explosion.world; ++ this.posX = explosion.x; ++ this.posY = explosion.y; ++ this.posZ = explosion.z; ++ this.minX = aabb.minX; ++ this.minY = aabb.minY; ++ this.minZ = aabb.minZ; ++ this.maxX = aabb.maxX; ++ this.maxY = aabb.maxY; ++ this.maxZ = aabb.maxZ; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ CacheKey cacheKey = (CacheKey) o; ++ ++ if (Double.compare(cacheKey.posX, posX) != 0) return false; ++ if (Double.compare(cacheKey.posY, posY) != 0) return false; ++ if (Double.compare(cacheKey.posZ, posZ) != 0) return false; ++ if (Double.compare(cacheKey.minX, minX) != 0) return false; ++ if (Double.compare(cacheKey.minY, minY) != 0) return false; ++ if (Double.compare(cacheKey.minZ, minZ) != 0) return false; ++ if (Double.compare(cacheKey.maxX, maxX) != 0) return false; ++ if (Double.compare(cacheKey.maxY, maxY) != 0) return false; ++ if (Double.compare(cacheKey.maxZ, maxZ) != 0) return false; ++ return world.equals(cacheKey.world); ++ } ++ ++ @Override ++ public int hashCode() { ++ int result; ++ long temp; ++ result = world.hashCode(); ++ temp = Double.doubleToLongBits(posX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ return result; ++ } ++ } ++ // Paper end + } diff --git a/patches/net/minecraft/world/ServerWorldEventHandler.java.patch b/patches/net/minecraft/world/ServerWorldEventHandler.java.patch new file mode 100644 index 00000000..918d4062 --- /dev/null +++ b/patches/net/minecraft/world/ServerWorldEventHandler.java.patch @@ -0,0 +1,42 @@ +--- ../src-base/minecraft/net/minecraft/world/ServerWorldEventHandler.java ++++ ../src-work/minecraft/net/minecraft/world/ServerWorldEventHandler.java +@@ -55,7 +55,8 @@ + + public void playSoundToAllNearExcept(@Nullable EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch) + { +- this.mcServer.getPlayerList().sendToAllNearExcept(player, x, y, z, volume > 1.0F ? (double)(16.0F * volume) : 16.0D, this.world.provider.getDimension(), new SPacketSoundEffect(soundIn, category, x, y, z, volume, pitch)); ++ // CraftBukkit - this.world.dimension, // Paper - this.world.dimension -> this.world ++ this.mcServer.getPlayerList().sendToAllNearExcept(player, x, y, z, volume > 1.0F ? (double)(16.0F * volume) : 16.0D, this.world, new SPacketSoundEffect(soundIn, category, x, y, z, volume, pitch)); + } + + public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) +@@ -77,7 +78,7 @@ + + public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data) + { +- this.mcServer.getPlayerList().sendToAllNearExcept(player, (double)blockPosIn.getX(), (double)blockPosIn.getY(), (double)blockPosIn.getZ(), 64.0D, this.world.provider.getDimension(), new SPacketEffect(type, blockPosIn, data, false)); ++ this.mcServer.getPlayerList().sendToAllNearExcept(player, (double)blockPosIn.getX(), (double)blockPosIn.getY(), (double)blockPosIn.getZ(), 64.0D, this.world, new SPacketEffect(type, blockPosIn, data, false)); + } + + public void broadcastSound(int soundID, BlockPos pos, int data) +@@ -87,6 +88,9 @@ + + public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress) + { ++ EntityPlayer entityhuman = null; ++ Entity entity = world.getEntityByID(breakerId); ++ if (entity instanceof EntityPlayer) entityhuman = (EntityPlayer) entity; + for (EntityPlayerMP entityplayermp : this.mcServer.getPlayerList().getPlayers()) + { + if (entityplayermp != null && entityplayermp.world == this.world && entityplayermp.getEntityId() != breakerId) +@@ -95,6 +99,10 @@ + double d1 = (double)pos.getY() - entityplayermp.posY; + double d2 = (double)pos.getZ() - entityplayermp.posZ; + ++ if (entityhuman != null && entityhuman instanceof EntityPlayerMP && !entityplayermp.getBukkitEntity().canSee(((EntityPlayerMP) entityhuman).getBukkitEntity())) { ++ continue; ++ } ++ + if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) + { + entityplayermp.connection.sendPacket(new SPacketBlockBreakAnim(breakerId, pos, progress)); diff --git a/patches/net/minecraft/world/Teleporter.java.patch b/patches/net/minecraft/world/Teleporter.java.patch new file mode 100644 index 00000000..b162d99b --- /dev/null +++ b/patches/net/minecraft/world/Teleporter.java.patch @@ -0,0 +1,362 @@ +--- ../src-base/minecraft/net/minecraft/world/Teleporter.java ++++ ../src-work/minecraft/net/minecraft/world/Teleporter.java +@@ -14,12 +14,17 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.ChunkPos; + import net.minecraft.util.math.MathHelper; ++import org.bukkit.Location; ++import org.bukkit.event.entity.EntityPortalExitEvent; ++import org.bukkit.util.Vector; + ++import javax.annotation.Nullable; ++ + public class Teleporter implements net.minecraftforge.common.util.ITeleporter + { + protected final WorldServer world; + protected final Random random; +- protected final Long2ObjectMap destinationCoordinateCache = new Long2ObjectOpenHashMap(4096); ++ protected final Long2ObjectMap destinationCoordinateCache = new Long2ObjectOpenHashMap(4096); + + public Teleporter(WorldServer worldIn) + { +@@ -42,74 +47,121 @@ + int i = MathHelper.floor(entityIn.posX); + int j = MathHelper.floor(entityIn.posY) - 1; + int k = MathHelper.floor(entityIn.posZ); +- int l = 1; +- int i1 = 0; ++ // CraftBukkit start - Modularize end portal creation ++ BlockPos created = this.createEndPortal(entityIn.posX, entityIn.posY, entityIn.posZ); ++ entityIn.setLocationAndAngles((double) created.getX(), (double) created.getY(), (double) created.getZ(), entityIn.rotationYaw, 0.0F); ++ entityIn.motionX = entityIn.motionY = entityIn.motionZ = 0.0D; ++ } ++ } + +- for (int j1 = -2; j1 <= 2; ++j1) ++ // Split out from original a(Entity, double, double, double, float) method in order to enable being called from createPortal ++ private BlockPos createEndPortal(double x, double y, double z) { ++ int i = MathHelper.floor(x); ++ int j = MathHelper.floor(y) - 1; ++ int k = MathHelper.floor(z); ++ // CraftBukkit end ++ int l = 1; ++ int i1 = 0; ++ ++ for (int j1 = -2; j1 <= 2; ++j1) ++ { ++ for (int k1 = -2; k1 <= 2; ++k1) + { +- for (int k1 = -2; k1 <= 2; ++k1) ++ for (int l1 = -1; l1 < 3; ++l1) + { +- for (int l1 = -1; l1 < 3; ++l1) +- { +- int i2 = i + k1 * 1 + j1 * 0; +- int j2 = j + l1; +- int k2 = k + k1 * 0 - j1 * 1; +- boolean flag = l1 < 0; +- this.world.setBlockState(new BlockPos(i2, j2, k2), flag ? Blocks.OBSIDIAN.getDefaultState() : Blocks.AIR.getDefaultState()); +- } ++ int i2 = i + k1 * 1 + j1 * 0; ++ int j2 = j + l1; ++ int k2 = k + k1 * 0 - j1 * 1; ++ boolean flag = l1 < 0; ++ this.world.setBlockState(new BlockPos(i2, j2, k2), flag ? Blocks.OBSIDIAN.getDefaultState() : Blocks.AIR.getDefaultState()); + } + } ++ } ++ // TODO: Is this a mistake? ++ return new BlockPos(i, k, k); ++ } + +- entityIn.setLocationAndAngles((double)i, (double)j, (double)k, entityIn.rotationYaw, 0.0F); +- entityIn.motionX = 0.0D; +- entityIn.motionY = 0.0D; +- entityIn.motionZ = 0.0D; ++ // use logic based on creation to verify end portal ++ @Nullable ++ private BlockPos findEndPortal(BlockPos portal) { ++ int i = portal.getX(); ++ int j = portal.getY() - 1; ++ int k = portal.getZ(); ++ byte b0 = 1; ++ byte b1 = 0; ++ ++ for (int l = -2; l <= 2; ++l) { ++ for (int i1 = -2; i1 <= 2; ++i1) { ++ for (int j1 = -1; j1 < 3; ++j1) { ++ int k1 = i + i1 * b0 + l * b1; ++ int l1 = j + j1; ++ int i2 = k + i1 * b1 - l * b0; ++ boolean flag = j1 < 0; ++ ++ if (this.world.getBlockState(new BlockPos(k1, l1, i2)).getBlock() != (flag ? Blocks.OBSIDIAN : Blocks.AIR)) { ++ return null; ++ } ++ } ++ } + } ++ return new BlockPos(i, j, k); + } + + public boolean placeInExistingPortal(Entity entityIn, float rotationYaw) + { +- int i = 128; ++ // CraftBukkit start - Modularize portal search process and entity teleportation ++ BlockPos found = this.findPortal(entityIn.posX, entityIn.posY, entityIn.posZ, 128); ++ if (found == null) { ++ return false; ++ } ++ ++ Location exit = new Location(this.world.getWorld(), found.getX(), found.getY(), found.getZ(), rotationYaw, entityIn.rotationPitch); ++ Vector velocity = entityIn.getBukkitEntity().getVelocity(); ++ this.adjustExit(entityIn, exit, velocity); ++ entityIn.setLocationAndAngles(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch()); ++ if (entityIn.motionX != velocity.getX() || entityIn.motionY != velocity.getY() || entityIn.motionZ != velocity.getZ()) { ++ entityIn.getBukkitEntity().setVelocity(velocity); ++ } ++ return true; ++ } ++ ++ @Nullable ++ public BlockPos findPortal(double x, double y, double z, int radius) { ++ if (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END) { ++ return this.findEndPortal(this.world.provider.getSpawnCoordinate()); ++ } ++ // CraftBukkit end + double d0 = -1.0D; +- int j = MathHelper.floor(entityIn.posX); +- int k = MathHelper.floor(entityIn.posZ); ++ int i = MathHelper.floor(x); ++ int j = MathHelper.floor(z); + boolean flag = true; + BlockPos blockpos = BlockPos.ORIGIN; +- long l = ChunkPos.asLong(j, k); ++ long l = ChunkPos.asLong(i, j); + +- if (this.destinationCoordinateCache.containsKey(l)) +- { +- Teleporter.PortalPosition teleporter$portalposition = (Teleporter.PortalPosition)this.destinationCoordinateCache.get(l); ++ if (this.destinationCoordinateCache.containsKey(l)) { ++ PortalPosition teleporter$portalposition = (PortalPosition) this.destinationCoordinateCache.get(l); + d0 = 0.0D; + blockpos = teleporter$portalposition; + teleporter$portalposition.lastUpdateTime = this.world.getTotalWorldTime(); + flag = false; +- } +- else +- { +- BlockPos blockpos3 = new BlockPos(entityIn); ++ } else { ++ BlockPos blockpos3 = new BlockPos(x, y, z); + +- for (int i1 = -128; i1 <= 128; ++i1) +- { ++ for (int i1 = -radius; i1 <= radius; ++i1) { + BlockPos blockpos2; + +- for (int j1 = -128; j1 <= 128; ++j1) +- { +- for (BlockPos blockpos1 = blockpos3.add(i1, this.world.getActualHeight() - 1 - blockpos3.getY(), j1); blockpos1.getY() >= 0; blockpos1 = blockpos2) +- { ++ for (int j1 = -radius; j1 <= radius; ++j1) { ++ for (BlockPos blockpos1 = blockpos3.add(i1, this.world.getActualHeight() - 1 - blockpos3.getY(), j1); blockpos1.getY() >= 0; blockpos1 = blockpos2) { + blockpos2 = blockpos1.down(); + +- if (this.world.getBlockState(blockpos1).getBlock() == Blocks.PORTAL) +- { +- for (blockpos2 = blockpos1.down(); this.world.getBlockState(blockpos2).getBlock() == Blocks.PORTAL; blockpos2 = blockpos2.down()) +- { ++ if (this.world.getBlockState(blockpos1).getBlock() == Blocks.PORTAL) { ++ for (blockpos2 = blockpos1.down(); this.world.getBlockState(blockpos2).getBlock() == Blocks.PORTAL; blockpos2 = blockpos2.down()) { + blockpos1 = blockpos2; + } + + double d1 = blockpos1.distanceSq(blockpos3); + +- if (d0 < 0.0D || d1 < d0) +- { ++ if (d0 < 0.0D || d1 < d0) { + d0 = d1; + blockpos = blockpos1; + } +@@ -119,16 +171,35 @@ + } + } + +- if (d0 >= 0.0D) +- { +- if (flag) +- { +- this.destinationCoordinateCache.put(l, new Teleporter.PortalPosition(blockpos, this.world.getTotalWorldTime())); ++ if (d0 >= 0.0D) { ++ if (flag) { ++ this.destinationCoordinateCache.put(l, new PortalPosition(blockpos, this.world.getTotalWorldTime())); + } ++ // CraftBukkit start - Move entity teleportation logic into exit ++ return blockpos; ++ } else { ++ return null; ++ } ++ } + +- double d5 = (double)blockpos.getX() + 0.5D; +- double d7 = (double)blockpos.getZ() + 0.5D; +- BlockPattern.PatternHelper blockpattern$patternhelper = Blocks.PORTAL.createPatternHelper(this.world, blockpos); ++ // Entity repositioning logic split out from original b method and combined with repositioning logic for The End from original a method ++ public void adjustExit(Entity entityIn, Location position, Vector velocity) { ++ Location from = position.clone(); ++ Vector before = velocity.clone(); ++ BlockPos object = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()); ++ float f33 = position.getYaw(); ++ ++ if (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END || entityIn.getBukkitEntity().getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END || entityIn.getLastPortalVec() == null) { ++ // entity.setPositionRotation((double) i, (double) j, (double) k, entity.yaw, 0.0F); ++ // entity.motX = entity.motY = entity.motZ = 0.0D; ++ position.setPitch(0.0F); ++ velocity.setX(0); ++ velocity.setY(0); ++ velocity.setZ(0); ++ } else { ++ double d5 = (double)object.getX() + 0.5D; ++ double d7 = (double)object.getZ() + 0.5D; ++ BlockPattern.PatternHelper blockpattern$patternhelper = Blocks.PORTAL.createPatternHelper(this.world, object); + boolean flag1 = blockpattern$patternhelper.getForwards().rotateY().getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE; + double d2 = blockpattern$patternhelper.getForwards().getAxis() == EnumFacing.Axis.X ? (double)blockpattern$patternhelper.getFrontTopLeft().getZ() : (double)blockpattern$patternhelper.getFrontTopLeft().getX(); + double d6 = (double)(blockpattern$patternhelper.getFrontTopLeft().getY() + 1) - entityIn.getLastPortalVec().y * (double)blockpattern$patternhelper.getHeight(); +@@ -173,36 +244,55 @@ + f3 = 1.0F; + } + +- double d3 = entityIn.motionX; +- double d4 = entityIn.motionZ; +- entityIn.motionX = d3 * (double)f + d4 * (double)f3; +- entityIn.motionZ = d3 * (double)f2 + d4 * (double)f1; +- entityIn.rotationYaw = rotationYaw - (float)(entityIn.getTeleportDirection().getOpposite().getHorizontalIndex() * 90) + (float)(blockpattern$patternhelper.getForwards().getHorizontalIndex() * 90); +- +- if (entityIn instanceof EntityPlayerMP) +- { +- ((EntityPlayerMP)entityIn).connection.setPlayerLocation(d5, d6, d7, entityIn.rotationYaw, entityIn.rotationPitch); +- } +- else +- { +- entityIn.setLocationAndAngles(d5, d6, d7, entityIn.rotationYaw, entityIn.rotationPitch); +- } +- +- return true; ++ // CraftBukkit start - Adjust position and velocity instances instead of entity ++ velocity.setX(velocity.getX() * (double) f + velocity.getZ() * (double) f3); ++ velocity.setZ(velocity.getX() * (double) f2 + velocity.getZ() * (double) f1); ++ f33 = f33 - (float) (entityIn.getTeleportDirection().getOpposite().getHorizontalIndex() * 90) + (float) (blockpattern$patternhelper.getForwards().getHorizontalIndex() * 90); ++ // entity.setPositionRotation(d2, d5, d3, entity.yaw, entity.pitch); ++ position.setX(d5); ++ position.setY(d6); ++ position.setZ(d7); ++ position.setYaw(f33); + } ++ EntityPortalExitEvent event = new EntityPortalExitEvent(entityIn.getBukkitEntity(), from, position, before, velocity); ++ this.world.getServer().getPluginManager().callEvent(event); ++ Location to = event.getTo(); ++ if (event.isCancelled() || to == null || !entityIn.isEntityAlive()) { ++ position.setX(from.getX()); ++ position.setY(from.getY()); ++ position.setZ(from.getZ()); ++ position.setYaw(from.getYaw()); ++ position.setPitch(from.getPitch()); ++ velocity.copy(before); ++ } + else + { +- return false; ++ position.setX(to.getX()); ++ position.setY(to.getY()); ++ position.setZ(to.getZ()); ++ position.setYaw(to.getYaw()); ++ position.setPitch(to.getPitch()); ++ velocity.copy(event.getAfter()); // event.getAfter() will never be null, as setAfter() will cause an NPE if null is passed in + } + } + + public boolean makePortal(Entity entityIn) + { ++ // CraftBukkit start - Allow for portal creation to be based on coordinates instead of entity ++ return this.createPortal(entityIn.posX, entityIn.posY, entityIn.posZ, 16); ++ } ++ ++ public boolean createPortal(double x, double y, double z, int b0) { ++ if (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END) { ++ createEndPortal(x, y, z); ++ return true; ++ } ++ // CraftBukkit end + int i = 16; + double d0 = -1.0D; +- int j = MathHelper.floor(entityIn.posX); +- int k = MathHelper.floor(entityIn.posY); +- int l = MathHelper.floor(entityIn.posZ); ++ int j = MathHelper.floor(x); ++ int k = MathHelper.floor(y); ++ int l = MathHelper.floor(z); + int i1 = j; + int j1 = k; + int k1 = l; +@@ -212,11 +302,11 @@ + + for (int j2 = j - 16; j2 <= j + 16; ++j2) + { +- double d1 = (double)j2 + 0.5D - entityIn.posX; ++ double d1 = (double)j2 + 0.5D - x; + + for (int l2 = l - 16; l2 <= l + 16; ++l2) + { +- double d2 = (double)l2 + 0.5D - entityIn.posZ; ++ double d2 = (double)l2 + 0.5D - z; + label293: + + for (int j3 = this.world.getActualHeight() - 1; j3 >= 0; --j3) +@@ -258,7 +348,7 @@ + } + } + +- double d5 = (double)j3 + 0.5D - entityIn.posY; ++ double d5 = (double)j3 + 0.5D - y; + double d7 = d1 * d1 + d5 * d5 + d2 * d2; + + if (d0 < 0.0D || d7 < d0) +@@ -279,11 +369,11 @@ + { + for (int l5 = j - 16; l5 <= j + 16; ++l5) + { +- double d3 = (double)l5 + 0.5D - entityIn.posX; ++ double d3 = (double)l5 + 0.5D - x; + + for (int j6 = l - 16; j6 <= l + 16; ++j6) + { +- double d4 = (double)j6 + 0.5D - entityIn.posZ; ++ double d4 = (double)j6 + 0.5D - z; + label231: + + for (int i7 = this.world.getActualHeight() - 1; i7 >= 0; --i7) +@@ -316,7 +406,7 @@ + } + } + +- double d6 = (double)i7 + 0.5D - entityIn.posY; ++ double d6 = (double)i7 + 0.5D - y; + double d8 = d3 * d3 + d6 * d6 + d4 * d4; + + if (d0 < 0.0D || d8 < d0) +@@ -404,11 +494,11 @@ + if (worldTime % 100L == 0L) + { + long i = worldTime - 300L; +- ObjectIterator objectiterator = this.destinationCoordinateCache.values().iterator(); ++ ObjectIterator objectiterator = this.destinationCoordinateCache.values().iterator(); + + while (objectiterator.hasNext()) + { +- Teleporter.PortalPosition teleporter$portalposition = (Teleporter.PortalPosition)objectiterator.next(); ++ PortalPosition teleporter$portalposition = (PortalPosition)objectiterator.next(); + + if (teleporter$portalposition == null || teleporter$portalposition.lastUpdateTime < i) + { diff --git a/patches/net/minecraft/world/World.java.patch b/patches/net/minecraft/world/World.java.patch new file mode 100644 index 00000000..f816b991 --- /dev/null +++ b/patches/net/minecraft/world/World.java.patch @@ -0,0 +1,904 @@ +--- ../src-base/minecraft/net/minecraft/world/World.java ++++ ../src-work/minecraft/net/minecraft/world/World.java +@@ -3,21 +3,20 @@ + import com.google.common.base.Function; + import com.google.common.base.MoreObjects; + import com.google.common.base.Predicate; ++import com.google.common.collect.ImmutableSetMultimap; + import com.google.common.collect.Lists; +-import java.util.Calendar; +-import java.util.Collection; +-import java.util.Iterator; +-import java.util.List; +-import java.util.Random; +-import java.util.UUID; +-import java.util.function.Supplier; + ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import java.util.*; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; + import javax.annotation.Nullable; ++ ++import com.google.common.collect.Maps; + import net.minecraft.advancements.AdvancementManager; + import net.minecraft.advancements.FunctionManager; + import net.minecraft.block.Block; + import net.minecraft.block.BlockLiquid; +-import net.minecraft.block.BlockObserver; + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.crash.CrashReport; +@@ -25,12 +24,22 @@ + import net.minecraft.crash.ICrashReportDetail; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityLiving; ++import net.minecraft.entity.EntityLivingBase; ++import net.minecraft.entity.item.EntityItem; ++import net.minecraft.entity.item.EntityXPOrb; ++import net.minecraft.entity.monster.EntityGhast; ++import net.minecraft.entity.monster.EntityGolem; ++import net.minecraft.entity.monster.EntityMob; ++import net.minecraft.entity.monster.EntitySlime; ++import net.minecraft.entity.passive.EntityAnimal; ++import net.minecraft.entity.passive.EntityWaterMob; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Biomes; + import net.minecraft.init.Blocks; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.Packet; ++import net.minecraft.network.play.server.SPacketWorldBorder; + import net.minecraft.pathfinding.PathWorldListener; + import net.minecraft.profiler.Profiler; + import net.minecraft.scoreboard.Scoreboard; +@@ -44,26 +53,39 @@ + import net.minecraft.util.ReportedException; + import net.minecraft.util.SoundCategory; + import net.minecraft.util.SoundEvent; +-import net.minecraft.util.math.AxisAlignedBB; +-import net.minecraft.util.math.BlockPos; +-import net.minecraft.util.math.MathHelper; +-import net.minecraft.util.math.RayTraceResult; +-import net.minecraft.util.math.Vec3d; ++import net.minecraft.util.math.*; + import net.minecraft.village.VillageCollection; + import net.minecraft.world.biome.Biome; + import net.minecraft.world.biome.BiomeProvider; ++import net.minecraft.world.border.IBorderListener; + import net.minecraft.world.border.WorldBorder; + import net.minecraft.world.chunk.Chunk; + import net.minecraft.world.chunk.IChunkProvider; ++import net.minecraft.world.gen.ChunkProviderServer; + import net.minecraft.world.gen.structure.StructureBoundingBox; + import net.minecraft.world.storage.ISaveHandler; + import net.minecraft.world.storage.MapStorage; + import net.minecraft.world.storage.WorldInfo; + import net.minecraft.world.storage.WorldSavedData; + import net.minecraft.world.storage.loot.LootTableManager; ++import net.minecraftforge.common.DimensionManager; ++import net.minecraftforge.common.ForgeChunkManager; ++import net.minecraftforge.common.ForgeChunkManager.Ticket; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Bukkit; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.entity.NPC; ++import org.bukkit.event.block.BlockCanBuildEvent; ++import org.bukkit.event.block.BlockPhysicsEvent; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.generator.ChunkGenerator; + ++ + public abstract class World implements IBlockAccess, net.minecraftforge.common.capabilities.ICapabilityProvider + { + /** +@@ -94,36 +116,149 @@ + public float thunderingStrength; + private int lastLightningBolt; + public final Random rand = new Random(); +- public final WorldProvider provider; ++ public WorldProvider provider; + protected PathWorldListener pathListener = new PathWorldListener(); + protected List eventListeners; + protected IChunkProvider chunkProvider; + protected final ISaveHandler saveHandler; +- protected WorldInfo worldInfo; ++ public WorldInfo worldInfo; + protected boolean findingSpawnPoint; +- protected MapStorage mapStorage; ++ public MapStorage mapStorage; + public VillageCollection villageCollection; + protected LootTableManager lootTable; + protected AdvancementManager advancementManager; + protected FunctionManager functionManager; + public final Profiler profiler; + private final Calendar calendar; +- protected Scoreboard worldScoreboard; ++ public Scoreboard worldScoreboard; + public final boolean isRemote; +- protected boolean spawnHostileMobs; +- protected boolean spawnPeacefulMobs; ++ public boolean spawnHostileMobs; ++ public boolean spawnPeacefulMobs; + private boolean processingLoadedTiles; + private final WorldBorder worldBorder; + int[] lightUpdateBlockList; + + public boolean restoringBlockSnapshots = false; + public boolean captureBlockSnapshots = false; +- public java.util.ArrayList capturedBlockSnapshots = new java.util.ArrayList(); ++ public ArrayList capturedBlockSnapshots = new ArrayList(); + private net.minecraftforge.common.capabilities.CapabilityDispatcher capabilities; + private net.minecraftforge.common.util.WorldCapabilityData capabilityData; + ++ private final CraftWorld world; ++ public boolean pvpMode; ++ public boolean keepSpawnInMemory = true; ++ public ChunkGenerator generator; ++ public boolean captureTreeGeneration = false; ++ public List captureDrops; ++ public long ticksPerAnimalSpawns; ++ public long ticksPerMonsterSpawns; ++ public boolean populating; ++ private int tickPosition; ++ public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot ++ public static boolean haveWeSilencedAPhysicsCrash; ++ public static String blockLocation; ++ private org.spigotmc.TickLimiter entityLimiter; ++ private org.spigotmc.TickLimiter tileLimiter; ++ private int tileTickPosition; ++ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions ++ private ImmutableSetMultimap forcedChunks = ImmutableSetMultimap.of(); ++ ++ public CraftWorld getWorld() { ++ return this.world; ++ } ++ ++ public CraftServer getServer() { ++ return (CraftServer) Bukkit.getServer(); ++ } ++ ++ public Chunk getChunkIfLoaded(int x, int z) { ++ return ((ChunkProviderServer) this.chunkProvider).getChunkIfLoaded(x, z); ++ } ++ ++ protected World(ISaveHandler saveHandlerIn, WorldInfo info, WorldProvider providerIn, Profiler profilerIn, boolean client, ChunkGenerator gen, org.bukkit.World.Environment env) { ++ this.spigotConfig = new org.spigotmc.SpigotWorldConfig( info.getWorldName() ); // Spigot ++ this.generator = gen; ++ this.world = new CraftWorld((WorldServer) this, gen, env); ++ this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit ++ this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); // CraftBukkit ++ // CraftBukkit end ++ this.eventListeners = Lists.newArrayList(this.pathListener); ++ this.calendar = Calendar.getInstance(); ++ this.worldScoreboard = new Scoreboard(); ++ this.spawnHostileMobs = true; ++ this.spawnPeacefulMobs = true; ++ this.lightUpdateBlockList = new int[32768]; ++ this.saveHandler = saveHandlerIn; ++ this.profiler = profilerIn; ++ this.worldInfo = info; ++ this.provider = providerIn; ++ this.isRemote = client; ++ this.worldBorder = providerIn.createWorldBorder(); ++ perWorldStorage = new MapStorage(null); ++ // Kettle start ++ if (DimensionManager.getWorld(0) != null) // if overworld has loaded, use its mapstorage ++ { ++ this.mapStorage = DimensionManager.getWorld(0).mapStorage; ++ } ++ else ++ { ++ this.mapStorage = new MapStorage(saveHandlerIn); ++ } ++ ++ if(this.worldInfo != null) // Use saved dimension from level.dat. Fixes issues with MultiVerse ++ { ++ if (this.worldInfo.getDimension() != 0) ++ this.provider.setDimension(this.worldInfo.getDimension()); ++ else ++ { ++ this.worldInfo.setDimension(this.provider.getDimension()); ++ } ++ } ++ ++ if (this.worldInfo.getDimension() == 0) ++ { ++ generator = this.getServer().getGenerator(this.worldInfo.getWorldName()); ++ getWorld().generator = generator; ++ } ++ // Kettle end ++ // CraftBukkit start ++ getWorldBorder().world = (WorldServer) this; ++ // From PlayerList.setPlayerFileData ++ getWorldBorder().addListener(new IBorderListener() { ++ public void onSizeChanged(WorldBorder worldborder, double d0) { ++ getServer().getHandle().sendAll(new SPacketWorldBorder(worldborder, SPacketWorldBorder.Action.SET_SIZE), worldborder.world); ++ } ++ ++ public void onTransitionStarted(WorldBorder worldborder, double d0, double d1, long i) { ++ getServer().getHandle().sendAll(new SPacketWorldBorder(worldborder, SPacketWorldBorder.Action.LERP_SIZE), worldborder.world); ++ } ++ ++ public void onCenterChanged(WorldBorder worldborder, double d0, double d1) { ++ getServer().getHandle().sendAll(new SPacketWorldBorder(worldborder, SPacketWorldBorder.Action.SET_CENTER), worldborder.world); ++ } ++ ++ public void onWarningTimeChanged(WorldBorder worldborder, int i) { ++ getServer().getHandle().sendAll(new SPacketWorldBorder(worldborder, SPacketWorldBorder.Action.SET_WARNING_TIME), worldborder.world); ++ } ++ ++ public void onWarningDistanceChanged(WorldBorder worldborder, int i) { ++ getServer().getHandle().sendAll(new SPacketWorldBorder(worldborder, SPacketWorldBorder.Action.SET_WARNING_BLOCKS), worldborder.world); ++ } ++ ++ public void onDamageAmountChanged(WorldBorder worldborder, double d0) {} ++ ++ public void onDamageBufferChanged(WorldBorder worldborder, double d0) {} ++ }); ++ this.getServer().addWorld(this.world); ++ // CraftBukkit end ++ this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); ++ this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); ++ } ++ + protected World(ISaveHandler saveHandlerIn, WorldInfo info, WorldProvider providerIn, Profiler profilerIn, boolean client) + { ++ this.spigotConfig = new org.spigotmc.SpigotWorldConfig( info.getWorldName() ); // Spigot ++ this.world = DimensionManager.getWorld(0) != null ? DimensionManager.getWorld(0).getWorld() : null; + this.eventListeners = Lists.newArrayList(this.pathListener); + this.calendar = Calendar.getInstance(); + this.worldScoreboard = new Scoreboard(); +@@ -136,7 +271,10 @@ + this.provider = providerIn; + this.isRemote = client; + this.worldBorder = providerIn.createWorldBorder(); +- perWorldStorage = new MapStorage((ISaveHandler)null); ++ perWorldStorage = DimensionManager.getWorld(0) != null ? DimensionManager.getWorld(0).mapStorage : new MapStorage(null); ++ this.mapStorage = DimensionManager.getWorld(0) != null ? DimensionManager.getWorld(0).mapStorage : new MapStorage(null); ++ this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); ++ this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } + + public World init() +@@ -298,7 +436,7 @@ + } + } + +- protected abstract boolean isChunkLoaded(int x, int z, boolean allowEmpty); ++ public abstract boolean isChunkLoaded(int x, int z, boolean allowEmpty); + + public Chunk getChunkFromBlockCoords(BlockPos pos) + { +@@ -317,6 +455,26 @@ + + public boolean setBlockState(BlockPos pos, IBlockState newState, int flags) + { ++ // CraftBukkit start - tree generation ++ if (this.captureTreeGeneration && Bukkit.isPrimaryThread()) { ++ net.minecraftforge.common.util.BlockSnapshot blocksnapshot = null; ++ ++ for (net.minecraftforge.common.util.BlockSnapshot previous : this.capturedBlockSnapshots) ++ { ++ if (previous.getPos().equals(pos)) ++ { ++ blocksnapshot = previous; ++ break; ++ } ++ } ++ if (blocksnapshot != null) ++ { ++ this.capturedBlockSnapshots.remove(blocksnapshot); ++ } ++ this.capturedBlockSnapshots.add(new net.minecraftforge.common.util.BlockSnapshot(this, pos, newState, flags)); ++ return true; ++ } ++ // CraftBukkit end + if (this.isOutsideBuildHeight(pos)) + { + return false; +@@ -437,6 +595,11 @@ + { + if (this.worldInfo.getTerrainType() != WorldType.DEBUG_ALL_BLOCK_STATES) + { ++ // CraftBukkit start ++ if (populating) { ++ return; ++ } ++ // CraftBukkit end + this.notifyNeighborsOfStateChange(pos, blockType, p_175722_3_); + } + } +@@ -548,6 +711,17 @@ + + try + { ++ // CraftBukkit start ++ CraftWorld world = ((WorldServer) this).getWorld(); ++ if (world != null) { ++ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftMagicNumbers.getId(blockIn)); ++ this.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + iblockstate.neighborChanged(this, pos, blockIn, fromPos); + } + catch (Throwable throwable) +@@ -586,6 +760,11 @@ + { + iblockstate.getBlock().observedNeighborChange(iblockstate, this, pos, p_190529_2_, p_190529_3_); + } ++ catch (StackOverflowError stackoverflowerror) { // Spigot Start ++ haveWeSilencedAPhysicsCrash = true; ++ blockLocation = pos.getX() + ", " + pos.getY() + ", " + pos.getZ(); ++ // Spigot End ++ } + catch (Throwable throwable) + { + CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Exception while updating neighbours"); +@@ -669,6 +848,41 @@ + } + } + ++ // Paper start - test if meets light level, return faster ++ // logic copied from below ++ public boolean isLightLevel(BlockPos blockposition, int level) { ++ if (blockposition.isValidLocation()) { ++ if (this.getBlockState(blockposition).useNeighborBrightness()) { ++ if (this.getLight(blockposition.up(), false) >= level) { ++ return true; ++ } ++ if (this.getLight(blockposition.east(), false) >= level) { ++ return true; ++ } ++ if (this.getLight(blockposition.west(), false) >= level) { ++ return true; ++ } ++ if (this.getLight(blockposition.south(), false) >= level) { ++ return true; ++ } ++ if (this.getLight(blockposition.north(), false) >= level) { ++ return true; ++ } ++ return false; ++ } else { ++ if (blockposition.getY() >= 256) { ++ blockposition = new BlockPos(blockposition.getX(), 255, blockposition.getZ()); ++ } ++ ++ Chunk chunk = this.getChunkFromBlockCoords(blockposition); ++ return chunk.getLightSubtracted(blockposition, this.getSkylightSubtracted()) >= level; ++ } ++ } else { ++ return true; ++ } ++ } ++ // Paper end ++ + public int getLightFromNeighbors(BlockPos pos) + { + return this.getLight(pos, true); +@@ -902,6 +1116,17 @@ + + public IBlockState getBlockState(BlockPos pos) + { ++ // CraftBukkit start - tree generation ++ if (captureTreeGeneration) ++ { ++ for (net.minecraftforge.common.util.BlockSnapshot blocksnapshot : this.capturedBlockSnapshots) ++ { ++ if (blocksnapshot.getPos().equals(pos)) { ++ return blocksnapshot.getReplacedBlock(); ++ } ++ } ++ } ++ // CraftBukkit end + if (this.isOutsideBuildHeight(pos)) + { + return Blocks.AIR.getDefaultState(); +@@ -1178,14 +1403,67 @@ + + public boolean spawnEntity(Entity entityIn) + { ++ // CraftBukkit start - Used for entities other than creatures ++ return spawnEntity(entityIn, CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public boolean spawnEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { ++ if (entity == null) return false; ++ ++ org.bukkit.event.Cancellable event = null; ++ if (entity instanceof EntityLivingBase && !(entity instanceof EntityPlayerMP)) { ++ boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterMob || entity instanceof EntityGolem; ++ boolean isMonster = entity instanceof EntityMob || entity instanceof EntityGhast || entity instanceof EntitySlime; ++ boolean isNpc = entity instanceof NPC; ++ ++ if (spawnReason != CreatureSpawnEvent.SpawnReason.CUSTOM) { ++ if (isAnimal && !spawnPeacefulMobs || isMonster && !spawnHostileMobs || isNpc && !getServer().getServer().getCanSpawnNPCs()) { ++ entity.isDead = true; ++ return false; ++ } ++ } ++ ++ event = CraftEventFactory.callCreatureSpawnEvent((EntityLivingBase) entity, spawnReason); ++ } else if (entity instanceof EntityItem) { ++ event = CraftEventFactory.callItemSpawnEvent((EntityItem) entity); ++ } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Projectile) { ++ // Not all projectiles extend EntityProjectile, so check for Bukkit interface instead ++ event = CraftEventFactory.callProjectileLaunchEvent(entity); ++ } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Vehicle){ ++ event = CraftEventFactory.callVehicleCreateEvent(entity); ++ } ++ ++ // Spigot start ++ else if (entity instanceof EntityXPOrb) { ++ EntityXPOrb xp = (EntityXPOrb) entity; ++ double radius = spigotConfig.expMerge; ++ if (radius > 0) { ++ List entities = this.getEntitiesWithinAABBExcludingEntity(entity, entity.getEntityBoundingBox().grow(radius, radius, radius)); ++ for (Entity e : entities) { ++ if (e instanceof EntityXPOrb) { ++ EntityXPOrb loopItem = (EntityXPOrb) e; ++ if (!loopItem.isDead) { ++ xp.xpValue += loopItem.xpValue; ++ loopItem.setDead(); ++ } ++ } ++ } ++ } ++ } // Spigot end ++ ++ if (event != null && (event.isCancelled() || entity.isDead)) { ++ entity.isDead = true; ++ return false; ++ } ++ // CraftBukkit end + // do not drop any items while restoring blocksnapshots. Prevents dupes +- if (!this.isRemote && (entityIn == null || (entityIn instanceof net.minecraft.entity.item.EntityItem && this.restoringBlockSnapshots))) return false; ++ if (!this.isRemote && (entity == null || (entity instanceof net.minecraft.entity.item.EntityItem && this.restoringBlockSnapshots))) return false; + +- int i = MathHelper.floor(entityIn.posX / 16.0D); +- int j = MathHelper.floor(entityIn.posZ / 16.0D); +- boolean flag = entityIn.forceSpawn; ++ int i = MathHelper.floor(entity.posX / 16.0D); ++ int j = MathHelper.floor(entity.posZ / 16.0D); ++ boolean flag = entity.forceSpawn; + +- if (entityIn instanceof EntityPlayer) ++ if (entity instanceof EntityPlayer) + { + flag = true; + } +@@ -1196,18 +1474,19 @@ + } + else + { +- if (entityIn instanceof EntityPlayer) ++ if (entity instanceof EntityPlayer) + { +- EntityPlayer entityplayer = (EntityPlayer)entityIn; ++ EntityPlayer entityplayer = (EntityPlayer)entity; + this.playerEntities.add(entityplayer); + this.updateAllPlayersSleepingFlag(); + } + +- if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityJoinWorldEvent(entityIn, this)) && !flag) return false; ++ if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityJoinWorldEvent(entity, this)) && !flag) return false; + +- this.getChunkFromChunkCoords(i, j).addEntity(entityIn); +- this.loadedEntityList.add(entityIn); +- this.onEntityAdded(entityIn); ++ this.getChunkFromChunkCoords(i, j).addEntity(entity); ++ if (entity.isDead) return false; // Paper - don't add dead entities, chunk registration may of killed it ++ this.loadedEntityList.add(entity); ++ this.onEntityAdded(entity); + return true; + } + } +@@ -1219,6 +1498,8 @@ + ((IWorldEventListener)this.eventListeners.get(i)).onEntityAdded(entityIn); + } + entityIn.onAddedToWorld(); ++ entityIn.valid = true; // CraftBukkit ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entityIn.getBukkitEntity()).callEvent(); // Paper - fire while valid + } + + public void onEntityRemoved(Entity entityIn) +@@ -1228,6 +1509,8 @@ + ((IWorldEventListener)this.eventListeners.get(i)).onEntityRemoved(entityIn); + } + entityIn.onRemovedFromWorld(); ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entityIn.getBukkitEntity()).callEvent(); // Paper - fire while valid ++ entityIn.valid = false; // CraftBukkit + } + + public void removeEntity(Entity entityIn) +@@ -1271,7 +1554,16 @@ + this.getChunkFromChunkCoords(i, j).removeEntity(entityIn); + } + +- this.loadedEntityList.remove(entityIn); ++ // this.loadedEntityList.remove(entityIn); ++ // CraftBukkit start - Decrement loop variable field if we've already ticked this entity ++ int index = this.loadedEntityList.indexOf(entityIn); ++ if (index != -1) { ++ if (index <= this.tickPosition) { ++ this.tickPosition--; ++ } ++ this.loadedEntityList.remove(index); ++ } ++ // CraftBukkit end + this.onEntityRemoved(entityIn); + } + +@@ -1674,7 +1966,11 @@ + for (int i = 0; i < this.weatherEffects.size(); ++i) + { + Entity entity = this.weatherEffects.get(i); +- ++ // CraftBukkit start - Fixed an NPE ++ if (entity == null) { ++ continue; ++ } ++ // CraftBukkit end + try + { + if(entity.updateBlocked) continue; +@@ -1711,7 +2007,12 @@ + } + + this.profiler.endStartSection("remove"); +- this.loadedEntityList.removeAll(this.unloadedEntityList); ++ //this.loadedEntityList.removeAll(this.unloadedEntityList); ++ // Paper start - Use alternate implementation with faster contains ++ java.util.Set toRemove = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); ++ toRemove.addAll(this.unloadedEntityList); ++ this.loadedEntityList.removeAll(toRemove); ++ // Paper end + + for (int k = 0; k < this.unloadedEntityList.size(); ++k) + { +@@ -1734,9 +2035,15 @@ + this.tickPlayers(); + this.profiler.endStartSection("regular"); + +- for (int i1 = 0; i1 < this.loadedEntityList.size(); ++i1) +- { +- Entity entity2 = this.loadedEntityList.get(i1); ++ org.spigotmc.ActivationRange.activateEntities(this); // Spigot ++ int entitiesThisCycle = 0; ++ if (tickPosition < 0) tickPosition = 0; ++ for (entityLimiter.initTick(); ++ entitiesThisCycle < loadedEntityList.size() && (entitiesThisCycle % 10 != 0 || entityLimiter.shouldContinue()); ++ tickPosition++, entitiesThisCycle++) { ++ tickPosition = (tickPosition < loadedEntityList.size()) ? tickPosition : 0; ++ Entity entity2 = (Entity) this.loadedEntityList.get(this.tickPosition); ++ // CraftBukkit end + Entity entity3 = entity2.getRidingEntity(); + + if (entity3 != null) +@@ -1787,7 +2094,7 @@ + this.getChunkFromChunkCoords(l1, i2).removeEntity(entity2); + } + +- this.loadedEntityList.remove(i1--); ++ this.loadedEntityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable + this.onEntityRemoved(entity2); + } + +@@ -1806,19 +2113,28 @@ + } + + // forge: faster "contains" makes this removal much more efficient ++ // Paper start - Use alternate implementation with faster contains + java.util.Set remove = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); + remove.addAll(tileEntitiesToBeRemoved); + this.tickableTileEntities.removeAll(remove); ++ // Paper end + this.loadedTileEntityList.removeAll(remove); + this.tileEntitiesToBeRemoved.clear(); + } + +- Iterator iterator = this.tickableTileEntities.iterator(); +- +- while (iterator.hasNext()) +- { +- TileEntity tileentity = iterator.next(); +- ++ int tilesThisCycle = 0; ++ for (tileLimiter.initTick(); ++ tilesThisCycle < this.tickableTileEntities.size() && (tilesThisCycle % 10 != 0 || tileLimiter.shouldContinue()); ++ tileTickPosition++, tilesThisCycle++) { ++ tileTickPosition = (tileTickPosition < tickableTileEntities.size()) ? tileTickPosition : 0; ++ TileEntity tileentity = (TileEntity) this.tickableTileEntities.get(tileTickPosition); ++ // Spigot start ++ if (tileentity == null) { ++ tilesThisCycle--; ++ this.tickableTileEntities.remove(tileTickPosition--); ++ continue; ++ } ++ // Spigot end + if (!tileentity.isInvalid() && tileentity.hasWorld()) + { + BlockPos blockpos = tileentity.getPos(); +@@ -1843,7 +2159,6 @@ + tileentity.addInfoToCrashReport(crashreportcategory2); + if (net.minecraftforge.common.ForgeModContainer.removeErroringTileEntities) + { +- net.minecraftforge.fml.common.FMLLog.log.fatal("{}", crashreport2.getCompleteReport()); + tileentity.invalidate(); + this.removeTileEntity(tileentity.getPos()); + } +@@ -1855,14 +2170,15 @@ + + if (tileentity.isInvalid()) + { +- iterator.remove(); ++ tilesThisCycle--; ++ this.tickableTileEntities.remove(tileTickPosition--); + this.loadedTileEntityList.remove(tileentity); + + if (this.isBlockLoaded(tileentity.getPos())) + { + //Forge: Bugfix: If we set the tile entity it immediately sets it in the chunk, so we could be desyned + Chunk chunk = this.getChunkFromBlockCoords(tileentity.getPos()); +- if (chunk.getTileEntity(tileentity.getPos(), net.minecraft.world.chunk.Chunk.EnumCreateEntityType.CHECK) == tileentity) ++ if (chunk.getTileEntity(tileentity.getPos(), Chunk.EnumCreateEntityType.CHECK) == tileentity) + chunk.removeTileEntity(tileentity.getPos()); + } + } +@@ -1879,10 +2195,12 @@ + + if (!tileentity1.isInvalid()) + { ++ /* CraftBukkit start - Order matters, moved down + if (!this.loadedTileEntityList.contains(tileentity1)) + { + this.addTileEntity(tileentity1); + } ++ // CraftBukkit end */ + + if (this.isBlockLoaded(tileentity1.getPos())) + { +@@ -1890,6 +2208,12 @@ + IBlockState iblockstate = chunk.getBlockState(tileentity1.getPos()); + chunk.addTileEntity(tileentity1.getPos(), tileentity1); + this.notifyBlockUpdate(tileentity1.getPos(), iblockstate, iblockstate, 3); ++ // CraftBukkit start ++ // From above, don't screw this up - SPIGOT-1746 ++ if (!this.loadedTileEntityList.contains(tileentity1)) { ++ this.addTileEntity(tileentity1); ++ } ++ // CraftBukkit end + } + } + } +@@ -1973,6 +2297,14 @@ + } + } + ++ // Spigot start ++ if (forceUpdate && !org.spigotmc.ActivationRange.checkIfActive(entityIn)) { ++ entityIn.ticksExisted++; ++ entityIn.inactiveTick(); ++ return; ++ } ++ // Spigot end ++ + entityIn.lastTickPosX = entityIn.posX; + entityIn.lastTickPosY = entityIn.posY; + entityIn.lastTickPosZ = entityIn.posZ; +@@ -1991,6 +2323,7 @@ + { + if(!entityIn.updateBlocked) + entityIn.onUpdate(); ++ entityIn.postTick(); + } + } + +@@ -2449,6 +2782,7 @@ + } + } + ++ tileEntityIn.setWorld(this); // Spigot - No null worlds + this.addedTileEntityList.add(tileEntityIn); + } + else +@@ -2657,6 +2991,13 @@ + } + + this.rainingStrength = MathHelper.clamp(this.rainingStrength, 0.0F, 1.0F); ++ // CraftBukkit start ++ for (int idx = 0; idx < this.playerEntities.size(); ++idx) { ++ if (((EntityPlayerMP) this.playerEntities.get(idx)).world == this) { ++ ((EntityPlayerMP) this.playerEntities.get(idx)).tickWeather(); ++ } ++ } ++ // CraftBukkit end + } + } + } +@@ -2844,10 +3185,14 @@ + } + } + +- public boolean checkLightFor(EnumSkyBlock lightType, BlockPos pos) ++ public boolean checkLightFor(EnumSkyBlock lightType, int x, int y, int z) + { +- if (!this.isAreaLoaded(pos, 16, false)) ++ // CraftBukkit start - Use neighbor cache instead of looking up ++ BlockPos pos = new BlockPos(x, y, z); ++ Chunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4); ++ if (chunk == null || !this.isAreaLoaded(pos, 16, false)) + { ++ // CraftBukkit end + return false; + } + else +@@ -2858,9 +3203,9 @@ + this.profiler.startSection("getBrightness"); + int l2 = this.getLightFor(lightType, pos); + int i3 = this.getRawLight(pos, lightType); +- int j3 = pos.getX(); +- int k3 = pos.getY(); +- int l3 = pos.getZ(); ++ int j3 = x; ++ int k3 = y; ++ int l3 = z; + + if (i3 > l2) + { +@@ -3137,7 +3482,16 @@ + + for (Entity entity4 : this.loadedEntityList) + { +- if ((!(entity4 instanceof EntityLiving) || !((EntityLiving)entity4).isNoDespawnRequired()) && entityType.isAssignableFrom(entity4.getClass())) ++ // CraftBukkit start - Split out persistent check, don't apply it to special persistent mobs ++ if (entity4 instanceof EntityLiving) { ++ EntityLiving entityinsentient = (EntityLiving) entity4; ++ if (entityinsentient.canDespawn() && entityinsentient.isNoDespawnRequired()) { ++ continue; ++ } ++ } ++ ++ // if ((!(entity4 instanceof EntityLiving) || !((EntityLiving)entity4).isNoDespawnRequired()) && entityType.isAssignableFrom(entity4.getClass())) ++ if (entityType.isAssignableFrom(entity4.getClass())) + { + ++j2; + } +@@ -3152,6 +3506,9 @@ + { + if (!net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityJoinWorldEvent(entity4, this))) + { ++ if (entity4 == null || entity4.isDead || entity4.valid) { // Paper - prevent adding already added or dead entities ++ continue; ++ } + loadedEntityList.add(entity4); + this.onEntityAdded(entity4); + } +@@ -3168,19 +3525,26 @@ + IBlockState iblockstate1 = this.getBlockState(pos); + AxisAlignedBB axisalignedbb = skipCollisionCheck ? null : blockIn.getDefaultState().getCollisionBoundingBox(this, pos); + ++ boolean defaultReturn; + if (!((placer instanceof EntityPlayer) || !net.minecraftforge.event.ForgeEventFactory.onBlockPlace(placer, new net.minecraftforge.common.util.BlockSnapshot(this, pos, blockIn.getDefaultState()), sidePlacedOn).isCanceled())) return false; + if (axisalignedbb != Block.NULL_AABB && !this.checkNoEntityCollision(axisalignedbb.offset(pos))) // Forge: Remove second parameter, we patch placer to be non-null, passing it here skips collision checks for the placer + { +- return false; ++ defaultReturn = false; + } + else if (iblockstate1.getMaterial() == Material.CIRCUITS && blockIn == Blocks.ANVIL) + { +- return true; ++ defaultReturn = true; + } + else + { +- return iblockstate1.getBlock().isReplaceable(this, pos) && blockIn.canPlaceBlockOnSide(this, pos, sidePlacedOn); ++ defaultReturn= iblockstate1.getBlock().isReplaceable(this, pos) && blockIn.canPlaceBlockOnSide(this, pos, sidePlacedOn); + } ++ ++ // CraftBukkit start - store default return ++ BlockCanBuildEvent event = new BlockCanBuildEvent(this.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftMagicNumbers.getId(blockIn), defaultReturn); ++ this.getServer().getPluginManager().callEvent(event); ++ return event.isBuildable(); ++ // CraftBukkit end + } + + public int getSeaLevel() +@@ -3345,6 +3709,12 @@ + { + EntityPlayer entityplayer1 = this.playerEntities.get(j2); + ++ // CraftBukkit start - Fixed an NPE ++ if (entityplayer1 == null || entityplayer1.isDead) { ++ continue; ++ } ++ // CraftBukkit end ++ + if (p_190525_9_.apply(entityplayer1)) + { + double d1 = entityplayer1.getDistanceSq(x, y, z); +@@ -3592,6 +3962,16 @@ + { + } + ++ // CraftBukkit start ++ // Calls the method that checks to see if players are sleeping ++ // Called by CraftPlayer.setPermanentSleeping() ++ public void checkSleepStatus() { ++ if (!this.isRemote) { ++ this.updateAllPlayersSleepingFlag(); ++ } ++ } ++ // CraftBukkit end ++ + public float getThunderStrength(float delta) + { + return (this.prevThunderingStrength + (this.thunderingStrength - this.prevThunderingStrength) * delta) * this.getRainStrength(delta); +@@ -3880,7 +4260,7 @@ + int j2 = x * 16 + 8 - blockpos1.getX(); + int k2 = z * 16 + 8 - blockpos1.getZ(); + int l2 = 128; +- return j2 >= -128 && j2 <= 128 && k2 >= -128 && k2 <= 128; ++ return j2 >= -128 && j2 <= 128 && k2 >= -128 && k2 <= 128 && this.keepSpawnInMemory; // CraftBukkit - Added 'this.keepSpawnInMemory' + } + + /* ======================================== FORGE START =====================================*/ +@@ -4016,4 +4396,48 @@ + { + return null; + } ++ ++ public ImmutableSetMultimap getForcedChunks() { ++ return forcedChunks; ++ } ++ ++ public void setForcedChunks(ImmutableSetMultimap forcedChunks) { ++ this.forcedChunks = forcedChunks; ++ } ++ ++ public ExecutorService lightingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("PaperSpigot - Lighting Thread").build()); // PaperSpigot - Asynchronous lighting updates ++ ++ /** ++ * PaperSpigot - Asynchronous lighting updates ++ */ ++ public boolean checkLightFor(EnumSkyBlock enumskyblock, BlockPos pos) { ++ Chunk chunk = this.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4); ++ if (chunk == null || !chunk.areNeighborsLoaded(1)) { ++ return false; ++ } ++ ++ List neighbors = new ArrayList(); ++ for (int cx = (pos.getX() >> 4) - 1; cx <= (pos.getX() >> 4) + 1; ++cx) { ++ for (int cz = (pos.getZ() >> 4) - 1; cz <= (pos.getZ() >> 4) + 1; ++cz) { ++ if (cx != pos.getX() >> 4 && cz != pos.getZ() >> 4) { ++ Chunk neighbor = this.getChunkIfLoaded(cx, cz); ++ if (neighbor != null) { ++ neighbors.add(neighbor); ++ } ++ } ++ } ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ return this.checkLightFor(enumskyblock, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ lightingExecutor.submit(new Runnable() { ++ @Override ++ public void run() { ++ World.this.checkLightFor(enumskyblock, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ }); ++ return true; ++ } + } diff --git a/patches/net/minecraft/world/WorldEntitySpawner.java.patch b/patches/net/minecraft/world/WorldEntitySpawner.java.patch new file mode 100644 index 00000000..19f264fa --- /dev/null +++ b/patches/net/minecraft/world/WorldEntitySpawner.java.patch @@ -0,0 +1,154 @@ +--- ../src-base/minecraft/net/minecraft/world/WorldEntitySpawner.java ++++ ../src-work/minecraft/net/minecraft/world/WorldEntitySpawner.java +@@ -1,9 +1,7 @@ + package net.minecraft.world; + + import com.google.common.collect.Sets; +-import java.util.List; +-import java.util.Random; +-import java.util.Set; ++import java.util.*; + import net.minecraft.block.Block; + import net.minecraft.block.BlockRailBase; + import net.minecraft.block.material.Material; +@@ -21,12 +19,33 @@ + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.biome.Biome; + import net.minecraft.world.chunk.Chunk; ++import net.minecraft.world.gen.ChunkProviderServer; ++import org.bukkit.event.entity.CreatureSpawnEvent; + + public final class WorldEntitySpawner + { + private static final int MOB_COUNT_DIV = (int)Math.pow(17.0D, 2.0D); + private final Set eligibleChunksForSpawning = Sets.newHashSet(); + ++ // Spigot start - get entity count only from chunks being processed in b ++ private int getEntityCount(WorldServer server, Class oClass) ++ { ++ int i = 0; ++ Iterator it = this.eligibleChunksForSpawning.iterator(); ++ while ( it.hasNext() ) ++ { ++ ChunkPos coord = it.next(); ++ int x = coord.x; ++ int z = coord.z; ++ if ( !((ChunkProviderServer)server.chunkProvider).droppedChunksSet.contains( coord ) && server.isChunkLoaded( x, z, true ) ) ++ { ++ i += Objects.requireNonNull(server.getChunkProvider().getLoadedChunk(x, z)).entityCount.get( oClass ); ++ } ++ } ++ return i; ++ } ++ // Spigot end ++ + public int findChunksForSpawning(WorldServer worldServerIn, boolean spawnHostileMobs, boolean spawnPeacefulMobs, boolean spawnOnSetTickRate) + { + if (!spawnHostileMobs && !spawnPeacefulMobs) +@@ -46,11 +65,14 @@ + int k = MathHelper.floor(entityplayer.posZ / 16.0D); + int l = 8; + +- for (int i1 = -8; i1 <= 8; ++i1) +- { +- for (int j1 = -8; j1 <= 8; ++j1) +- { +- boolean flag = i1 == -8 || i1 == 8 || j1 == -8 || j1 == 8; ++ // Spigot Start ++ byte b0 = worldServerIn.spigotConfig.mobSpawnRange; ++ b0 = ( b0 > worldServerIn.spigotConfig.viewDistance ) ? (byte) worldServerIn.spigotConfig.viewDistance : b0; ++ b0 = ( b0 > 8 ) ? 8 : b0; ++ for (int i1 = -b0; i1 <= b0; ++i1) { ++ for (int j1 = -b0; j1 <= b0; ++j1) { ++ boolean flag = i1 == -b0 || i1 == b0 || j1 == -b0 || j1 == b0; ++ // Spigot End + ChunkPos chunkpos = new ChunkPos(i1 + j, j1 + k); + + if (!this.eligibleChunksForSpawning.contains(chunkpos)) +@@ -77,20 +99,46 @@ + + for (EnumCreatureType enumcreaturetype : EnumCreatureType.values()) + { ++ // CraftBukkit start - Use per-world spawn limits ++ int limit = enumcreaturetype.getMaxNumberOfCreature(); ++ switch (enumcreaturetype) { ++ case MONSTER: ++ limit = worldServerIn.getWorld().getMonsterSpawnLimit(); ++ break; ++ case CREATURE: ++ limit = worldServerIn.getWorld().getAnimalSpawnLimit(); ++ break; ++ case WATER_CREATURE: ++ limit = worldServerIn.getWorld().getWaterAnimalSpawnLimit(); ++ break; ++ case AMBIENT: ++ limit = worldServerIn.getWorld().getAmbientSpawnLimit(); ++ break; ++ } ++ ++ if (limit == 0) { ++ continue; ++ } ++ int mobcnt = 0; // Spigot ++ // CraftBukkit end + if ((!enumcreaturetype.getPeacefulCreature() || spawnPeacefulMobs) && (enumcreaturetype.getPeacefulCreature() || spawnHostileMobs) && (!enumcreaturetype.getAnimal() || spawnOnSetTickRate)) + { +- int k4 = worldServerIn.countEntities(enumcreaturetype, true); +- int l4 = enumcreaturetype.getMaxNumberOfCreature() * i / MOB_COUNT_DIV; ++ /* Paper start - As far as I can tell neither of these are even used ++ int k4 = worldServerIn.countEntities(enumcreaturetype.getCreatureClass()); ++ // int l4 = enumcreaturetype.getMaxNumberOfCreature() * i / MOB_COUNT_DIV; ++ int l4 = limit * i / MOB_COUNT_DIV; // CraftBukkit - use per-world limits ++ */ // Paper end + +- if (k4 <= l4) ++ if ((mobcnt = getEntityCount(worldServerIn, enumcreaturetype.getCreatureClass())) <= limit * i / 256) + { +- java.util.ArrayList shuffled = com.google.common.collect.Lists.newArrayList(this.eligibleChunksForSpawning); +- java.util.Collections.shuffle(shuffled); + BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos(); ++ Iterator iterator = this.eligibleChunksForSpawning.iterator(); ++ int moblimit = (limit * i / 256) - mobcnt + 1; // Spigot - up to 1 more than limit + label134: + +- for (ChunkPos chunkpos1 : shuffled) ++ while(iterator.hasNext() && (moblimit >0)) // Spigot - while more allowed + { ++ ChunkPos chunkpos1 = iterator.next(); + BlockPos blockpos = getRandomChunkPosition(worldServerIn, chunkpos1.x, chunkpos1.z); + int k1 = blockpos.getX(); + int l1 = blockpos.getY(); +@@ -156,15 +204,17 @@ + + if (entityliving.isNotColliding()) + { +- ++j2; +- worldServerIn.spawnEntity(entityliving); ++ if (worldServerIn.spawnEntity(entityliving, CreatureSpawnEvent.SpawnReason.NATURAL)) { ++ ++j2; ++ moblimit--; // Spigot ++ } + } + else + { + entityliving.setDead(); + } + +- if (j2 >= net.minecraftforge.event.ForgeEventFactory.getMaxSpawnPackSize(entityliving)) ++ if (moblimit <= 0 || j2 >= net.minecraftforge.event.ForgeEventFactory.getMaxSpawnPackSize(entityliving)) + { + continue label134; + } +@@ -295,8 +345,11 @@ + + if (net.minecraftforge.event.ForgeEventFactory.canEntitySpawn(entityliving, worldIn, j + 0.5f, (float) blockpos.getY(), k +0.5f, false) == net.minecraftforge.fml.common.eventhandler.Event.Result.DENY) continue; + entityliving.setLocationAndAngles((double)((float)j + 0.5F), (double)blockpos.getY(), (double)((float)k + 0.5F), randomIn.nextFloat() * 360.0F, 0.0F); +- worldIn.spawnEntity(entityliving); ++ // CraftBukkit start - Added a reason for spawning this creature, moved entityliving.onInitialSpawn(ientitylivingdata) up ++ // worldIn.spawnEntity(entityliving); + ientitylivingdata = entityliving.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityliving)), ientitylivingdata); ++ worldIn.spawnEntity(entityliving, CreatureSpawnEvent.SpawnReason.CHUNK_GEN); ++ // CraftBukkit end + flag = true; + } + diff --git a/patches/net/minecraft/world/WorldProvider.java.patch b/patches/net/minecraft/world/WorldProvider.java.patch new file mode 100644 index 00000000..6082f351 --- /dev/null +++ b/patches/net/minecraft/world/WorldProvider.java.patch @@ -0,0 +1,60 @@ +--- ../src-base/minecraft/net/minecraft/world/WorldProvider.java ++++ ../src-work/minecraft/net/minecraft/world/WorldProvider.java +@@ -9,13 +9,9 @@ + import net.minecraft.util.math.Vec3d; + import net.minecraft.world.biome.Biome; + import net.minecraft.world.biome.BiomeProvider; +-import net.minecraft.world.biome.BiomeProviderSingle; + import net.minecraft.world.border.WorldBorder; +-import net.minecraft.world.gen.ChunkGeneratorDebug; +-import net.minecraft.world.gen.ChunkGeneratorFlat; +-import net.minecraft.world.gen.ChunkGeneratorOverworld; +-import net.minecraft.world.gen.FlatGeneratorInfo; + import net.minecraft.world.gen.IChunkGenerator; ++import net.minecraftforge.common.DimensionManager; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + +@@ -412,7 +408,7 @@ + */ + public WorldSleepResult canSleepAt(net.minecraft.entity.player.EntityPlayer player, BlockPos pos) + { +- return (this.canRespawnHere() && this.world.getBiome(pos) != net.minecraft.init.Biomes.HELL) ? WorldSleepResult.ALLOW : WorldSleepResult.BED_EXPLODES; ++ return (this.canRespawnHere() && this.world.getBiome(pos) != Biomes.HELL) ? WorldSleepResult.ALLOW : WorldSleepResult.BED_EXPLODES; + } + + public static enum WorldSleepResult +@@ -565,10 +561,20 @@ + + public void resetRainAndThunder() + { +- world.worldInfo.setRainTime(0); + world.worldInfo.setRaining(false); +- world.worldInfo.setThunderTime(0); ++ // CraftBukkit start ++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. ++ // Not that everyone ever manages to get the whole server to sleep at the same time.... ++ if (!world.worldInfo.isRaining()) { ++ world.worldInfo.setRainTime(0); ++ } ++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. ++ // Not that everyone ever manages to get the whole server to sleep at the same time.... + world.worldInfo.setThundering(false); ++ if (!world.worldInfo.isThundering()) { ++ world.worldInfo.setThunderTime(0); ++ } ++ // CraftBukkit end + } + + public boolean canDoLightning(net.minecraft.world.chunk.Chunk chunk) +@@ -603,4 +609,10 @@ + { + return true; + } ++ ++ // Kettle start ++ public static WorldProvider getProviderForDimension(int dimensionId){ ++ return DimensionManager.createProviderFor(dimensionId); ++ } ++ // Kettle end + } diff --git a/patches/net/minecraft/world/WorldProviderHell.java.patch b/patches/net/minecraft/world/WorldProviderHell.java.patch new file mode 100644 index 00000000..c7bea196 --- /dev/null +++ b/patches/net/minecraft/world/WorldProviderHell.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/world/WorldProviderHell.java ++++ ../src-work/minecraft/net/minecraft/world/WorldProviderHell.java +@@ -72,11 +72,13 @@ + { + public double getCenterX() + { +- return super.getCenterX() / 8.0D; ++ // return super.getCenterX() / 8.0D; ++ return super.getCenterX(); // CraftBukkit + } + public double getCenterZ() + { +- return super.getCenterZ() / 8.0D; ++ // return super.getCenterZ() / 8.0D; ++ return super.getCenterZ(); // CraftBukkit + } + }; + } diff --git a/patches/net/minecraft/world/WorldServer.java.patch b/patches/net/minecraft/world/WorldServer.java.patch new file mode 100644 index 00000000..b1832a4a --- /dev/null +++ b/patches/net/minecraft/world/WorldServer.java.patch @@ -0,0 +1,781 @@ +--- ../src-base/minecraft/net/minecraft/world/WorldServer.java ++++ ../src-work/minecraft/net/minecraft/world/WorldServer.java +@@ -14,27 +14,22 @@ + import java.util.Set; + import java.util.TreeSet; + import java.util.UUID; +-import java.util.function.Predicate; ++import java.util.logging.Level; + import java.util.stream.Collectors; + import javax.annotation.Nullable; + import net.minecraft.advancements.AdvancementManager; + import net.minecraft.advancements.FunctionManager; +-import net.minecraft.block.Block; +-import net.minecraft.block.BlockEventData; ++import net.minecraft.block.*; + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.entity.Entity; +-import net.minecraft.entity.EntityList; + import net.minecraft.entity.EntityLivingBase; + import net.minecraft.entity.EntityTracker; + import net.minecraft.entity.EnumCreatureType; +-import net.minecraft.entity.INpc; + import net.minecraft.entity.effect.EntityLightningBolt; +-import net.minecraft.entity.passive.EntityAnimal; + import net.minecraft.entity.passive.EntitySkeletonHorse; +-import net.minecraft.entity.passive.EntityWaterMob; + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Blocks; +@@ -50,6 +45,7 @@ + import net.minecraft.scoreboard.ServerScoreboard; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.management.PlayerChunkMap; ++import net.minecraft.tileentity.*; + import net.minecraft.util.EnumParticleTypes; + import net.minecraft.util.IProgressUpdate; + import net.minecraft.util.IThreadListener; +@@ -77,16 +73,20 @@ + import net.minecraft.world.storage.WorldInfo; + import net.minecraft.world.storage.WorldSavedDataCallableSave; + import net.minecraft.world.storage.loot.LootTableManager; ++import net.minecraftforge.common.DimensionManager; ++import net.minecraftforge.common.WorldSpecificSaveHandler; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.weather.LightningStrikeEvent; + + public class WorldServer extends World implements IThreadListener + { + private static final Logger LOGGER = LogManager.getLogger(); + private final MinecraftServer mcServer; +- private final EntityTracker entityTracker; ++ public EntityTracker entityTracker; + private final PlayerChunkMap playerChunkMap; + private final Set pendingTickListEntriesHashSet = Sets.newHashSet(); + private final TreeSet pendingTickListEntriesTreeSet = new TreeSet(); +@@ -97,7 +97,7 @@ + private final Teleporter worldTeleporter; + private final WorldEntitySpawner entitySpawner = new WorldEntitySpawner(); + protected final VillageSiege villageSiege = new VillageSiege(this); +- private final WorldServer.ServerBlockEventList[] blockEventQueue = new WorldServer.ServerBlockEventList[] {new WorldServer.ServerBlockEventList(), new WorldServer.ServerBlockEventList()}; ++ private final ServerBlockEventList[] blockEventQueue = new ServerBlockEventList[] {new ServerBlockEventList(), new ServerBlockEventList()}; + private int blockEventCacheIndex; + private final List pendingTickListEntriesThisTick = Lists.newArrayList(); + +@@ -105,22 +105,62 @@ + protected Set doneChunks = new java.util.HashSet(); + public List customTeleporters = new ArrayList(); + ++ public final int dimension; ++ ++ public WorldServer(MinecraftServer server, ISaveHandler saveHandlerIn, WorldInfo info, int dimensionId, Profiler methodprofiler, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { ++ super(saveHandlerIn, info, net.minecraftforge.common.DimensionManager.createProviderFor(dimensionId), methodprofiler, false, gen, env); ++ this.pvpMode = server.isPVPEnabled(); ++ info.world = this; ++ this.dimension = dimensionId; ++ this.mcServer = server; ++ this.entityTracker = new EntityTracker(this); ++ this.playerChunkMap = new PlayerChunkMap(this); ++ // Guarantee the dimension ID was not reset by the provider ++ int providerDim = this.provider.getDimension(); ++ this.provider.setWorld(this); ++ this.provider.setDimension(providerDim); ++ this.chunkProvider = this.createChunkProvider(); ++ this.worldTeleporter = new org.bukkit.craftbukkit.CraftTravelAgent(this); //// CraftBukkit ++ this.calculateInitialSkylight(); ++ this.calculateInitialWeather(); ++ this.getWorldBorder().setSize(server.getMaxWorldSize()); ++ WorldServer overworld = DimensionManager.getWorld(0); ++ if (overworld != null) { ++ this.mapStorage = overworld.mapStorage; ++ this.perWorldStorage = new MapStorage(new WorldSpecificSaveHandler(this, overworld.saveHandler)); ++ } ++ else { ++ this.mapStorage = new MapStorage(saveHandlerIn); ++ this.perWorldStorage = new MapStorage(new WorldSpecificSaveHandler(this, this.saveHandler)); ++ } ++ net.minecraftforge.common.DimensionManager.setWorld(dimensionId, this, mcServer); ++ } ++ + public WorldServer(MinecraftServer server, ISaveHandler saveHandlerIn, WorldInfo info, int dimensionId, Profiler profilerIn) + { + super(saveHandlerIn, info, net.minecraftforge.common.DimensionManager.createProviderFor(dimensionId), profilerIn, false); + this.mcServer = server; + this.entityTracker = new EntityTracker(this); + this.playerChunkMap = new PlayerChunkMap(this); ++ this.dimension = dimensionId; + // Guarantee the dimension ID was not reset by the provider + int providerDim = this.provider.getDimension(); + this.provider.setWorld(this); + this.provider.setDimension(providerDim); + this.chunkProvider = this.createChunkProvider(); +- perWorldStorage = new MapStorage(new net.minecraftforge.common.WorldSpecificSaveHandler((WorldServer)this, saveHandlerIn)); + this.worldTeleporter = new Teleporter(this); + this.calculateInitialSkylight(); + this.calculateInitialWeather(); + this.getWorldBorder().setSize(server.getMaxWorldSize()); ++ WorldServer overworld = DimensionManager.getWorld(0); ++ if (overworld != null) { ++ this.mapStorage = overworld.mapStorage; ++ this.perWorldStorage = new MapStorage(new WorldSpecificSaveHandler(this, overworld.saveHandler)); ++ } ++ else { ++ this.mapStorage = new MapStorage(saveHandlerIn); ++ this.perWorldStorage = new MapStorage(new WorldSpecificSaveHandler(this, this.saveHandler)); ++ } + net.minecraftforge.common.DimensionManager.setWorld(dimensionId, this, mcServer); + } + +@@ -140,7 +180,7 @@ + this.villageCollection = villagecollection; + this.villageCollection.setWorldsForAll(this); + } +- ++ if (getServer().getScoreboardManager() == null) { // CraftBukkit + this.worldScoreboard = new ServerScoreboard(this.mcServer); + ScoreboardSaveData scoreboardsavedata = (ScoreboardSaveData)this.mapStorage.getOrLoadData(ScoreboardSaveData.class, "scoreboard"); + +@@ -152,8 +192,18 @@ + + scoreboardsavedata.setScoreboard(this.worldScoreboard); + ((ServerScoreboard)this.worldScoreboard).addDirtyRunnable(new WorldSavedDataCallableSave(scoreboardsavedata)); ++ } else { ++ this.worldScoreboard = getServer().getScoreboardManager().getMainScoreboard().getHandle(); ++ } + this.lootTable = new LootTableManager(new File(new File(this.saveHandler.getWorldDirectory(), "data"), "loot_tables")); +- this.advancementManager = new AdvancementManager(new File(new File(this.saveHandler.getWorldDirectory(), "data"), "advancements")); ++ // CraftBukkit start ++ if (this.dimension != 0) { // SPIGOT-3899 multiple worlds of advancements not supported ++ this.advancementManager = this.mcServer.getAdvancementManager(); ++ } ++ if (this.advancementManager == null) { ++ this.advancementManager = new AdvancementManager(new File(new File(this.saveHandler.getWorldDirectory(), "data"), "advancements")); ++ } ++ // CraftBukkit end + this.functionManager = new FunctionManager(new File(new File(this.saveHandler.getWorldDirectory(), "data"), "functions"), this.mcServer); + this.getWorldBorder().setCenter(this.worldInfo.getBorderCenterX(), this.worldInfo.getBorderCenterZ()); + this.getWorldBorder().setDamageAmount(this.worldInfo.getBorderDamagePerBlock()); +@@ -171,9 +221,145 @@ + } + + this.initCapabilities(); ++ + return this; + } + ++ @Override ++ public TileEntity getTileEntity(BlockPos pos) { ++ TileEntity result = super.getTileEntity(pos); ++ Block type = getBlockState(pos).getBlock(); ++ ++ if (type == Blocks.CHEST) { ++ if (!(result instanceof TileEntityChest)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.FURNACE) { ++ if (!(result instanceof TileEntityFurnace)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.DROPPER) { ++ if (!(result instanceof TileEntityDropper)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.DISPENSER) { ++ if (!(result instanceof TileEntityDispenser)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.JUKEBOX) { ++ if (!(result instanceof BlockJukebox.TileEntityJukebox)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.NOTEBLOCK) { ++ if (!(result instanceof TileEntityNote)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.MOB_SPAWNER) { ++ if (!(result instanceof TileEntityMobSpawner)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if ((type == Blocks.STANDING_SIGN) || (type == Blocks.WALL_SIGN)) { ++ if (!(result instanceof TileEntitySign)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.ENDER_CHEST) { ++ if (!(result instanceof TileEntityEnderChest)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.BREWING_STAND) { ++ if (!(result instanceof TileEntityBrewingStand)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.BEACON) { ++ if (!(result instanceof TileEntityBeacon)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.HOPPER) { ++ if (!(result instanceof TileEntityHopper)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.ENCHANTING_TABLE) { ++ if (!(result instanceof TileEntityEnchantmentTable)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.END_PORTAL) { ++ if (!(result instanceof TileEntityEndPortal)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.SKULL) { ++ if (!(result instanceof TileEntitySkull)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.DAYLIGHT_DETECTOR || type == Blocks.DAYLIGHT_DETECTOR_INVERTED) { ++ if (!(result instanceof TileEntityDaylightDetector)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.POWERED_COMPARATOR || type == Blocks.UNPOWERED_COMPARATOR) { ++ if (!(result instanceof TileEntityComparator)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.FLOWER_POT) { ++ if (!(result instanceof TileEntityFlowerPot)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.STANDING_BANNER || type == Blocks.WALL_BANNER) { ++ if (!(result instanceof TileEntityBanner)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.STRUCTURE_BLOCK) { ++ if (!(result instanceof TileEntityStructure)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.END_GATEWAY) { ++ if (!(result instanceof TileEntityEndGateway)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.COMMAND_BLOCK) { ++ if (!(result instanceof TileEntityCommandBlock)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.STRUCTURE_BLOCK) { ++ if (!(result instanceof TileEntityStructure)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } else if (type == Blocks.BED) { ++ if (!(result instanceof TileEntityBed)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } ++ // Paper Start - add TE fix checks for shulkers, see nms.BlockShulkerBox ++ else if (type instanceof BlockShulkerBox) { ++ if (!(result instanceof TileEntityShulkerBox)) { ++ result = fixTileEntity(pos, type, result); ++ } ++ } ++ // Paper end ++ ++ return result; ++ } ++ ++ private TileEntity fixTileEntity(BlockPos pos, Block type, TileEntity found) { ++ this.getServer().getLogger().log(Level.SEVERE, "Block at {0},{1},{2} is {3} but has {4}" + ". " ++ + "Bukkit will attempt to fix this, but there may be additional damage that we cannot recover.", new Object[]{pos.getX(), pos.getY(), pos.getZ(), org.bukkit.Material.getMaterial(Block.getIdFromBlock(type)).toString(), found}); ++ if (type instanceof ITileEntityProvider) { ++ TileEntity replacement = ((ITileEntityProvider) type).createNewTileEntity(this, type.getMetaFromState(this.getBlockState(pos))); ++ replacement.world = this; ++ this.setTileEntity(pos, replacement); ++ return replacement; ++ } else { ++ this.getServer().getLogger().severe("Don't know how to fix for this type... Can't do anything! :("); ++ return found; ++ } ++ } ++ ++ private boolean canSpawn(int x, int z) { ++ if (this.generator != null) { ++ return this.generator.canSpawn(this.getWorld(), x, z); ++ } else { ++ return this.provider.canCoordinateBeSpawn(x, z); ++ } ++ } ++ + public void tick() + { + super.tick(); +@@ -198,9 +384,12 @@ + + this.profiler.startSection("mobSpawner"); + +- if (this.getGameRules().getBoolean("doMobSpawning") && this.worldInfo.getTerrainType() != WorldType.DEBUG_ALL_BLOCK_STATES) ++ // CraftBukkit start - Only call spawner if we have players online and the world allows for mobs or animals ++ long time = this.worldInfo.getWorldTotalTime(); ++ if (this.getGameRules().getBoolean("doMobSpawning") && this.worldInfo.getTerrainType() != WorldType.DEBUG_ALL_BLOCK_STATES && (this.spawnHostileMobs || this.spawnPeacefulMobs) && this.playerEntities.size() > 0) + { +- this.entitySpawner.findChunksForSpawning(this, this.spawnHostileMobs, this.spawnPeacefulMobs, this.worldInfo.getWorldTotalTime() % 400L == 0L); ++ this.entitySpawner.findChunksForSpawning(this, this.spawnHostileMobs && (this.ticksPerMonsterSpawns != 0 && time % this.ticksPerMonsterSpawns == 0L), this.spawnPeacefulMobs && (this.ticksPerAnimalSpawns != 0 && time % this.ticksPerAnimalSpawns == 0L), this.worldInfo.getWorldTotalTime() % 400L == 0L); ++ // CraftBukkit end + } + + this.profiler.endStartSection("chunkSource"); +@@ -212,7 +401,7 @@ + this.setSkylightSubtracted(j); + } + +- this.worldInfo.setWorldTotalTime(this.worldInfo.getWorldTotalTime() + 1L); ++ this.worldInfo.tick(); + + if (this.getGameRules().getBoolean("doDaylightCycle")) + { +@@ -236,6 +425,7 @@ + } + this.profiler.endSection(); + this.sendQueuedBlockEvents(); ++ this.getWorld().processChunkGC(); + } + + @Nullable +@@ -268,7 +458,7 @@ + { + ++i; + } +- else if (entityplayer.isPlayerSleeping()) ++ else if (entityplayer.isPlayerSleeping() || entityplayer.fauxSleeping) + { + ++j; + } +@@ -298,19 +488,26 @@ + this.provider.resetRainAndThunder(); + } + ++ // TODO: Test this method + public boolean areAllPlayersAsleep() + { + if (this.allPlayersSleeping && !this.isRemote) + { ++ // CraftBukkit - This allows us to assume that some people are in bed but not really, allowing time to pass in spite of AFKers ++ boolean foundActualSleepers = false; ++ + for (EntityPlayer entityplayer : this.playerEntities) + { +- if (!entityplayer.isSpectator() && !entityplayer.isPlayerFullyAsleep()) ++ if (entityplayer.isPlayerFullyAsleep()) { ++ foundActualSleepers = true; ++ } ++ if (!entityplayer.isSpectator() && !entityplayer.isPlayerFullyAsleep() || entityplayer.fauxSleeping) + { + return false; + } + } + +- return true; ++ return foundActualSleepers; + } + else + { +@@ -346,7 +543,7 @@ + this.worldInfo.setSpawnZ(j); + } + +- protected boolean isChunkLoaded(int x, int z, boolean allowEmpty) ++ public boolean isChunkLoaded(int x, int z, boolean allowEmpty) + { + return this.getChunkProvider().chunkExists(x, z); + } +@@ -355,7 +552,7 @@ + { + this.profiler.startSection("playerCheckLight"); + +- if (!this.playerEntities.isEmpty()) ++ if (spigotConfig.randomLightUpdates && !this.playerEntities.isEmpty()) // Spigot + { + int i = this.rand.nextInt(this.playerEntities.size()); + EntityPlayer entityplayer = this.playerEntities.get(i); +@@ -392,12 +589,14 @@ + { + this.profiler.startSection("getChunk"); + Chunk chunk = iterator.next(); ++ try { + int j = chunk.x * 16; + int k = chunk.z * 16; + this.profiler.endStartSection("checkNextLight"); + chunk.enqueueRelightChecks(); + this.profiler.endStartSection("tickChunk"); + chunk.onTick(false); ++// if (!chunk.areNeighborsLoaded(1)) continue; // Spigot + this.profiler.endStartSection("thunder"); + + if (this.provider.canDoLightning(chunk) && flag && flag1 && this.rand.nextInt(100000) == 0) +@@ -416,7 +615,7 @@ + entityskeletonhorse.setTrap(true); + entityskeletonhorse.setGrowingAge(0); + entityskeletonhorse.setPosition((double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ()); +- this.spawnEntity(entityskeletonhorse); ++ this.spawnEntity(entityskeletonhorse, CreatureSpawnEvent.SpawnReason.LIGHTNING); + this.addWeatherEffect(new EntityLightningBolt(this, (double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ(), true)); + } + else +@@ -438,12 +637,14 @@ + if (this.isAreaLoaded(blockpos2, 1)) // Forge: check area to avoid loading neighbors in unloaded chunks + if (this.canBlockFreezeNoWater(blockpos2)) + { +- this.setBlockState(blockpos2, Blocks.ICE.getDefaultState()); ++ // this.setBlockState(blockpos2, Blocks.ICE.getDefaultState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockpos2, Blocks.ICE.getDefaultState(), null); + } + + if (flag && this.canSnowAt(blockpos1, true)) + { +- this.setBlockState(blockpos1, Blocks.SNOW_LAYER.getDefaultState()); ++ // this.setBlockState(blockpos1, Blocks.SNOW_LAYER.getDefaultState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockpos1, Blocks.SNOW_LAYER.getDefaultState(), null); + } + + if (flag && this.getBiome(blockpos2).canRain()) +@@ -481,8 +682,15 @@ + } + } + } ++ } catch (Exception e) { ++ try { ++ this.getChunkProvider().unloadChunk(chunk, true); ++ } catch (Exception e2) { ++ e.printStackTrace(); ++ } ++ e.printStackTrace(); + } +- ++ } + this.profiler.endSection(); + } + } +@@ -611,6 +819,7 @@ + + this.provider.onWorldUpdateEntities(); + super.updateEntities(); ++ spigotConfig.currentPrimedTnt = 0; // Spigot + } + + protected void tickPlayers() +@@ -686,15 +895,20 @@ + { + int i = this.pendingTickListEntriesTreeSet.size(); + +- if (i != this.pendingTickListEntriesHashSet.size()) +- { +- throw new IllegalStateException("TickNextTick list out of synch"); ++ if (i != this.pendingTickListEntriesHashSet.size()) { ++ this.pendingTickListEntriesHashSet.clear(); ++ this.pendingTickListEntriesHashSet.addAll(this.pendingTickListEntriesTreeSet); + } +- else +- { + if (i > 65536) + { +- i = 65536; ++ // i = 65536; ++ // CraftBukkit start - If the server has too much to process over time, try to alleviate that ++ if (i > 20 * 65536) { ++ i = i / 20; ++ } else { ++ i = 65536; ++ } ++ // CraftBukkit end + } + + this.profiler.startSection("cleaning"); +@@ -756,7 +970,6 @@ + return !this.pendingTickListEntriesTreeSet.isEmpty(); + } + } +- } + + @Nullable + public List getPendingBlockUpdates(Chunk chunkIn, boolean remove) +@@ -796,7 +1009,8 @@ + { + if (remove) + { +- if (i == 0) ++ this.pendingTickListEntriesHashSet.remove(nextticklistentry); ++ if (i != 0) + { + this.pendingTickListEntriesHashSet.remove(nextticklistentry); + } +@@ -806,7 +1020,7 @@ + + if (list == null) + { +- list = Lists.newArrayList(); ++ list = new ArrayList(30); + } + + list.add(nextticklistentry); +@@ -817,21 +1031,6 @@ + return list; + } + +- public void updateEntityWithOptionalForce(Entity entityIn, boolean forceUpdate) +- { +- if (!this.canSpawnAnimals() && (entityIn instanceof EntityAnimal || entityIn instanceof EntityWaterMob)) +- { +- entityIn.setDead(); +- } +- +- if (!this.canSpawnNPCs() && entityIn instanceof INpc) +- { +- entityIn.setDead(); +- } +- +- super.updateEntityWithOptionalForce(entityIn, forceUpdate); +- } +- + private boolean canSpawnNPCs() + { + return this.mcServer.getCanSpawnNPCs(); +@@ -845,9 +1044,62 @@ + protected IChunkProvider createChunkProvider() + { + IChunkLoader ichunkloader = this.saveHandler.getChunkLoader(this.provider); +- return new ChunkProviderServer(this, ichunkloader, this.provider.createChunkGenerator()); ++ // Kettle - if provider is vanilla, proceed to create a bukkit compatible chunk generator ++ if (this.provider.getClass().toString().length() <= 3 || this.provider.getClass().toString().contains("net.minecraft")) { ++ // CraftBukkit start ++ org.bukkit.craftbukkit.generator.InternalChunkGenerator gen; ++ ++ if (this.generator != null) { ++ gen = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, this.getSeed(), this.generator); ++ } else if (this.provider instanceof WorldProviderHell) { ++ gen = new org.bukkit.craftbukkit.generator.NetherChunkGenerator(this, this.getSeed()); ++ } else if (this.provider instanceof WorldProviderEnd) { ++ gen = new org.bukkit.craftbukkit.generator.SkyLandsChunkGenerator(this, this.getSeed()); ++ } else { ++ gen = new org.bukkit.craftbukkit.generator.NormalChunkGenerator(this, this.getSeed()); ++ } ++ ++ this.chunkProvider = new ChunkProviderServer(this, ichunkloader, gen); ++ // CraftBukkit end ++ } else { // custom provider, load normally for forge compatibility ++ this.chunkProvider = new ChunkProviderServer(this, ichunkloader, this.provider.createChunkGenerator()); ++ } ++ return chunkProvider; + } + ++ public List getTileEntities(int i, int j, int k, int l, int i1, int j1) { ++ ArrayList arraylist = Lists.newArrayList(); ++ ++ // CraftBukkit start - Get tile entities from chunks instead of world ++ for (int chunkX = (i >> 4); chunkX <= ((l - 1) >> 4); chunkX++) { ++ for (int chunkZ = (k >> 4); chunkZ <= ((j1 - 1) >> 4); chunkZ++) { ++ Chunk chunk = getChunkFromChunkCoords(chunkX, chunkZ); ++ if (chunk == null) { ++ continue; ++ } ++ for (Object te : chunk.getTileEntityMap().values()) { ++ TileEntity tileentity = (TileEntity) te; ++ if ((tileentity.getPos().getX() >= i) && (tileentity.getPos().getY() >= j) && (tileentity.getPos().getZ() >= k) && (tileentity.getPos().getX() < l) && (tileentity.getPos().getY() < i1) && (tileentity.getPos().getZ() < j1)) { ++ arraylist.add(tileentity); ++ } ++ } ++ } ++ } ++ /* ++ for (int k1 = 0; k1 < this.tileEntityList.size(); ++k1) { ++ TileEntity tileentity = (TileEntity) this.tileEntityList.get(k1); ++ BlockPosition blockposition = tileentity.getPosition(); ++ ++ if (blockposition.getX() >= i && blockposition.getY() >= j && blockposition.getZ() >= k && blockposition.getX() < l && blockposition.getY() < i1 && blockposition.getZ() < j1) { ++ arraylist.add(tileentity); ++ } ++ } ++ */ ++ // CraftBukkit end ++ ++ return arraylist; ++ } ++ + public boolean isBlockModifiable(EntityPlayer player, BlockPos pos) + { + return super.isBlockModifiable(player, pos); +@@ -929,6 +1181,23 @@ + int j = this.provider.getAverageGroundLevel(); + int k = 8; + ++ // CraftBukkit start ++ if (this.generator != null) { ++ Random rand = new Random(this.getSeed()); ++ org.bukkit.Location spawn = this.generator.getFixedSpawnLocation(((WorldServer) this).getWorld(), rand); ++ ++ if (spawn != null) { ++ if (spawn.getWorld() != ((WorldServer) this).getWorld()) { ++ throw new IllegalStateException("Cannot set spawn point for " + this.worldInfo.getWorldName() + " to be in another world (" + spawn.getWorld().getName() + ")"); ++ } else { ++ this.worldInfo.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ())); ++ this.findingSpawnPoint = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end ++ + if (blockpos != null) + { + i = blockpos.getX(); +@@ -941,7 +1210,7 @@ + + int l = 0; + +- while (!this.provider.canCoordinateBeSpawn(i, k)) ++ while (!this.canSpawn(i, k)) // CraftBukkit - use our own canSpawn + { + i += random.nextInt(64) - random.nextInt(64); + k += random.nextInt(64) - random.nextInt(64); +@@ -992,6 +1261,7 @@ + + if (chunkproviderserver.canSave()) + { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); + if (progressCallback != null) + { + progressCallback.displaySavingString("Saving level"); +@@ -1006,14 +1276,6 @@ + + chunkproviderserver.saveChunks(all); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.WorldEvent.Save(this)); +- +- for (Chunk chunk : Lists.newArrayList(chunkproviderserver.getLoadedChunks())) +- { +- if (chunk != null && !this.playerChunkMap.contains(chunk.x, chunk.z)) +- { +- chunkproviderserver.queueUnload(chunk); +- } +- } + } + } + +@@ -1039,6 +1301,12 @@ + } + } + ++ // CraftBukkit start - Save secondary data for nether/end ++ if (this instanceof WorldServerMulti) { ++ ((WorldServerMulti) this).saveAdditionalData(); ++ } ++ // CraftBukkit end ++ + this.worldInfo.setBorderSize(this.getWorldBorder().getDiameter()); + this.worldInfo.getBorderCenterX(this.getWorldBorder().getCenterX()); + this.worldInfo.getBorderCenterZ(this.getWorldBorder().getCenterZ()); +@@ -1058,6 +1326,12 @@ + return this.canAddEntity(entityIn) ? super.spawnEntity(entityIn) : false; + } + ++ public boolean spawnEntity(Entity entityIn, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // World.spawnEntity(Entity) will call this, and we still want to perform ++ // existing entity checking when it's called with a SpawnReason ++ return this.canAddEntity(entityIn) ? super.spawnEntity(entityIn, spawnReason) : false; ++ } ++ + public void loadEntities(Collection entityCollection) + { + for (Entity entity : Lists.newArrayList(entityCollection)) +@@ -1074,7 +1348,6 @@ + { + if (entityIn.isDead) + { +- LOGGER.warn("Tried to add entity {} but it was marked as removed already", (Object)EntityList.getKey(entityIn)); + return false; + } + else +@@ -1085,7 +1358,7 @@ + { + Entity entity = this.entitiesByUuid.get(uuid); + +- if (this.unloadedEntityList.contains(entity)) ++ if (this.unloadedEntityList.contains(entity) || entity.isDead) // Paper - if dupe is dead, overwrite + { + this.unloadedEntityList.remove(entity); + } +@@ -1093,7 +1366,6 @@ + { + if (!(entityIn instanceof EntityPlayer)) + { +- LOGGER.warn("Keeping entity {} that already exists with UUID {}", EntityList.getKey(entity), uuid.toString()); + return false; + } + +@@ -1141,9 +1413,15 @@ + + public boolean addWeatherEffect(Entity entityIn) + { ++ LightningStrikeEvent lightning = new LightningStrikeEvent(this.getWorld(), (org.bukkit.entity.LightningStrike) entityIn.getBukkitEntity()); ++ this.getServer().getPluginManager().callEvent(lightning); ++ ++ if (lightning.isCancelled()) { ++ return false; ++ } + if (super.addWeatherEffect(entityIn)) + { +- this.mcServer.getPlayerList().sendToAllNearExcept((EntityPlayer)null, entityIn.posX, entityIn.posY, entityIn.posZ, 512.0D, this.provider.getDimension(), new SPacketSpawnGlobalEntity(entityIn)); ++ this.mcServer.getPlayerList().sendToAllNearExcept((EntityPlayer)null, entityIn.posX, entityIn.posY, entityIn.posZ, 512.0D, this, new SPacketSpawnGlobalEntity(entityIn)); // CraftBukkit - Use dimension // Paper - use world instead of dimension + return true; + } + else +@@ -1164,11 +1442,12 @@ + + public Explosion newExplosion(@Nullable Entity entityIn, double x, double y, double z, float strength, boolean isFlaming, boolean isSmoking) + { +- Explosion explosion = new Explosion(this, entityIn, x, y, z, strength, isFlaming, isSmoking); +- if (net.minecraftforge.event.ForgeEventFactory.onExplosionStart(this, explosion)) return explosion; +- explosion.doExplosionA(); +- explosion.doExplosionB(false); ++ Explosion explosion = super.newExplosion(entityIn, x, y, z, strength, isFlaming, isSmoking); + ++ if (explosion.wasCanceled) { ++ return explosion; ++ } ++ if (net.minecraftforge.event.ForgeEventFactory.onExplosionStart(this, explosion)) return explosion; + if (!isSmoking) + { + explosion.clearAffectedBlockPositions(); +@@ -1211,7 +1490,8 @@ + { + if (this.fireBlockEvent(blockeventdata)) + { +- this.mcServer.getPlayerList().sendToAllNearExcept((EntityPlayer)null, (double)blockeventdata.getPosition().getX(), (double)blockeventdata.getPosition().getY(), (double)blockeventdata.getPosition().getZ(), 64.0D, this.provider.getDimension(), new SPacketBlockAction(blockeventdata.getPosition(), blockeventdata.getBlock(), blockeventdata.getEventID(), blockeventdata.getEventParameter())); ++ // CraftBukkit - this.provider.dimension -> this.dimension // Paper - dimension -> world ++ this.mcServer.getPlayerList().sendToAllNearExcept((EntityPlayer)null, (double)blockeventdata.getPosition().getX(), (double)blockeventdata.getPosition().getY(), (double)blockeventdata.getPosition().getZ(), 64.0D, this, new SPacketBlockAction(blockeventdata.getPosition(), blockeventdata.getBlock(), blockeventdata.getEventID(), blockeventdata.getEventParameter())); + } + } + +@@ -1298,11 +1578,19 @@ + + public void spawnParticle(EnumParticleTypes particleType, boolean longDistance, double xCoord, double yCoord, double zCoord, int numberOfParticles, double xOffset, double yOffset, double zOffset, double particleSpeed, int... particleArguments) + { ++ // CraftBukkit - visibility api support ++ sendParticles(null, particleType, longDistance, xCoord, yCoord, zCoord, numberOfParticles, xOffset, yOffset, zOffset, particleSpeed, particleArguments); ++ } ++ ++ // Paper start - Particle API Expansion ++ public void sendParticles(@Nullable EntityPlayerMP sender, EnumParticleTypes particleType, boolean longDistance, double xCoord, double yCoord, double zCoord, int numberOfParticles, double xOffset, double yOffset, double zOffset, double particleSpeed, int... particleArguments) { ++ // CraftBukkit end + SPacketParticles spacketparticles = new SPacketParticles(particleType, longDistance, (float)xCoord, (float)yCoord, (float)zCoord, (float)xOffset, (float)yOffset, (float)zOffset, (float)particleSpeed, numberOfParticles, particleArguments); + + for (int i = 0; i < this.playerEntities.size(); ++i) + { + EntityPlayerMP entityplayermp = (EntityPlayerMP)this.playerEntities.get(i); ++ if (sender != null && !entityplayermp.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; + this.sendPacketWithinDistance(entityplayermp, longDistance, xCoord, yCoord, zCoord, spacketparticles); + } + } +@@ -1367,4 +1655,5 @@ + { + } + } ++ + } diff --git a/patches/net/minecraft/world/WorldServerMulti.java.patch b/patches/net/minecraft/world/WorldServerMulti.java.patch new file mode 100644 index 00000000..661de383 --- /dev/null +++ b/patches/net/minecraft/world/WorldServerMulti.java.patch @@ -0,0 +1,67 @@ +--- ../src-base/minecraft/net/minecraft/world/WorldServerMulti.java ++++ ../src-work/minecraft/net/minecraft/world/WorldServerMulti.java +@@ -7,12 +7,54 @@ + import net.minecraft.world.border.WorldBorder; + import net.minecraft.world.storage.DerivedWorldInfo; + import net.minecraft.world.storage.ISaveHandler; ++import net.minecraft.world.storage.WorldInfo; + + public class WorldServerMulti extends WorldServer + { + private final WorldServer delegate; + private IBorderListener borderListener; + ++ // CraftBukkit start - Add WorldInfo, Environment and ChunkGenerator arguments ++ public WorldServerMulti(MinecraftServer server, ISaveHandler saveHandlerIn, int dimensionId, WorldServer delegate, Profiler profilerIn, WorldInfo worldData, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) ++ { ++ super(server, saveHandlerIn, worldData, dimensionId, profilerIn, env, gen); ++ this.delegate = delegate; ++ /* CraftBukkit start ++ this.borderListener = new IBorderListener() ++ { ++ public void onSizeChanged(WorldBorder border, double newSize) ++ { ++ WorldServerMulti.this.getWorldBorder().setTransition(newSize); ++ } ++ public void onTransitionStarted(WorldBorder border, double oldSize, double newSize, long time) ++ { ++ WorldServerMulti.this.getWorldBorder().setTransition(oldSize, newSize, time); ++ } ++ public void onCenterChanged(WorldBorder border, double x, double z) ++ { ++ WorldServerMulti.this.getWorldBorder().setCenter(x, z); ++ } ++ public void onWarningTimeChanged(WorldBorder border, int newTime) ++ { ++ WorldServerMulti.this.getWorldBorder().setWarningTime(newTime); ++ } ++ public void onWarningDistanceChanged(WorldBorder border, int newDistance) ++ { ++ WorldServerMulti.this.getWorldBorder().setWarningDistance(newDistance); ++ } ++ public void onDamageAmountChanged(WorldBorder border, double newAmount) ++ { ++ WorldServerMulti.this.getWorldBorder().setDamageAmount(newAmount); ++ } ++ public void onDamageBufferChanged(WorldBorder border, double newSize) ++ { ++ WorldServerMulti.this.getWorldBorder().setDamageBuffer(newSize); ++ } ++ }; ++ this.delegate.getWorldBorder().addListener(this.borderListener); ++ // CraftBukkit end */ ++ } ++ + public WorldServerMulti(MinecraftServer server, ISaveHandler saveHandlerIn, int dimensionId, WorldServer delegate, Profiler profilerIn) + { + super(server, saveHandlerIn, new DerivedWorldInfo(delegate.getWorldInfo()), dimensionId, profilerIn); +@@ -77,7 +119,8 @@ + } + + this.initCapabilities(); +- return this; ++ // return this; ++ return super.init(); // CraftBukkit + } + + diff --git a/patches/net/minecraft/world/biome/Biome.java.patch b/patches/net/minecraft/world/biome/Biome.java.patch new file mode 100644 index 00000000..bd2a8005 --- /dev/null +++ b/patches/net/minecraft/world/biome/Biome.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraft/world/biome/Biome.java ++++ ../src-work/minecraft/net/minecraft/world/biome/Biome.java +@@ -49,6 +49,7 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public abstract class Biome extends net.minecraftforge.registries.IForgeRegistryEntry.Impl + { +@@ -182,7 +183,7 @@ + return this.spawnableCaveCreatureList; + default: + // Forge: Return a non-empty list for non-vanilla EnumCreatureTypes +- if (!this.modSpawnableLists.containsKey(creatureType)) this.modSpawnableLists.put(creatureType, Lists.newArrayList()); ++ if (!this.modSpawnableLists.containsKey(creatureType)) this.modSpawnableLists.put(creatureType, Lists.newArrayList()); + return this.modSpawnableLists.get(creatureType); + } + } +@@ -475,7 +476,8 @@ + return; + } + +- world.setBlockState(pos, flower.state, 3); ++// world.setBlockState(pos, flower.state, 3); ++ CraftEventFactory.handleBlockGrowEvent(world, pos.getX(), pos.getY(), pos.getZ(), flower.state.getBlock(), flower.state.getBlock().getMetaFromState(flower.state)); + } + + /* ========================================= FORGE END ======================================*/ diff --git a/patches/net/minecraft/world/border/WorldBorder.java.patch b/patches/net/minecraft/world/border/WorldBorder.java.patch new file mode 100644 index 00000000..e44c07b1 --- /dev/null +++ b/patches/net/minecraft/world/border/WorldBorder.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraft/world/border/WorldBorder.java ++++ ../src-work/minecraft/net/minecraft/world/border/WorldBorder.java +@@ -6,6 +6,7 @@ + import net.minecraft.util.math.AxisAlignedBB; + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.ChunkPos; ++import net.minecraft.world.WorldServer; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + +@@ -24,6 +25,9 @@ + private int warningTime; + private int warningDistance; + ++ // TODO: Check if it's initialized properly ++ public WorldServer world; ++ + public WorldBorder() + { + this.endDiameter = this.startDiameter; +@@ -206,6 +210,7 @@ + + public void addListener(IBorderListener listener) + { ++ if (listeners.contains(listener)) return; + this.listeners.add(listener); + } + diff --git a/patches/net/minecraft/world/chunk/BlockStateContainer.java.patch b/patches/net/minecraft/world/chunk/BlockStateContainer.java.patch new file mode 100644 index 00000000..65f1e16a --- /dev/null +++ b/patches/net/minecraft/world/chunk/BlockStateContainer.java.patch @@ -0,0 +1,22 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/BlockStateContainer.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/BlockStateContainer.java +@@ -164,7 +164,18 @@ + int l = i >> 4 & 15; + int i1 = blockIdExtension == null ? 0 : blockIdExtension.get(j, k, l); + int j1 = i1 << 12 | (blockIds[i] & 255) << 4 | data.get(j, k, l); +- this.set(i, Block.BLOCK_STATE_IDS.getByValue(j1)); ++ IBlockState state = Block.BLOCK_STATE_IDS.getByValue(j1); ++ if (state == null) { ++ Block block = Block.getBlockById(j1 >> 4); ++ if (block != null) { ++ try { ++ state = block.getDefaultState(); ++ } catch (Exception ignored) { ++ state = block.getDefaultState(); ++ } ++ } ++ } ++ this.set(i, state); + } + } + diff --git a/patches/net/minecraft/world/chunk/Chunk.java.patch b/patches/net/minecraft/world/chunk/Chunk.java.patch new file mode 100644 index 00000000..7f810d7d --- /dev/null +++ b/patches/net/minecraft/world/chunk/Chunk.java.patch @@ -0,0 +1,403 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/Chunk.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/Chunk.java +@@ -8,18 +8,23 @@ + import java.util.Map; + import java.util.Random; + import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.stream.Collectors; + import javax.annotation.Nullable; + import net.minecraft.block.Block; +-import net.minecraft.block.ITileEntityProvider; ++import net.minecraft.block.BlockSand; + import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.crash.ICrashReportDetail; + import net.minecraft.entity.Entity; ++import net.minecraft.entity.EntityLiving; ++import net.minecraft.entity.EnumCreatureType; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.init.Biomes; + import net.minecraft.init.Blocks; + import net.minecraft.network.PacketBuffer; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.tileentity.TileEntity; + import net.minecraft.util.ClassInheritanceMultiMap; + import net.minecraft.util.EnumFacing; +@@ -40,6 +45,7 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Server; + + public class Chunk implements net.minecraftforge.common.capabilities.ICapabilityProvider + { +@@ -50,13 +56,13 @@ + private final int[] precipitationHeightMap; + private final boolean[] updateSkylightColumns; + private boolean loaded; +- private final World world; +- private final int[] heightMap; ++ public final World world; ++ public final int[] heightMap; + public final int x; + public final int z; + private boolean isGapLightingUpdated; +- private final Map tileEntities; +- private final ClassInheritanceMultiMap[] entityLists; ++ public final Map tileEntities; ++ public final ClassInheritanceMultiMap[] entityLists; // Spigot + private boolean isTerrainPopulated; + private boolean isLightPopulated; + private boolean ticked; +@@ -68,7 +74,36 @@ + private int queuedLightChecks; + private final ConcurrentLinkedQueue tileEntityPosQueue; + public boolean unloadQueued; ++ public gnu.trove.map.hash.TObjectIntHashMap entityCount = new gnu.trove.map.hash.TObjectIntHashMap(); // Spigot ++ // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking ++ private int neighbors = 0x1 << 12; ++ public long chunkKey; + ++ public boolean areNeighborsLoaded(final int radius) { ++ switch (radius) { ++ case 2: ++ return this.neighbors == Integer.MAX_VALUE >> 6; ++ case 1: ++ final int mask = ++ // x z offset x z offset x z offset ++ (0x1 << (1 * 5 + 1 + 12)) | (0x1 << (0 * 5 + 1 + 12)) | (0x1 << (-1 * 5 + 1 + 12)) | ++ (0x1 << (1 * 5 + 0 + 12)) | (0x1 << (0 * 5 + 0 + 12)) | (0x1 << (-1 * 5 + 0 + 12)) | ++ (0x1 << (1 * 5 + -1 + 12)) | (0x1 << (0 * 5 + -1 + 12)) | (0x1 << (-1 * 5 + -1 + 12)); ++ return (this.neighbors & mask) == mask; ++ default: ++ throw new UnsupportedOperationException(String.valueOf(radius)); ++ } ++ } ++ ++ public void setNeighborLoaded(final int x, final int z) { ++ this.neighbors |= 0x1 << (x * 5 + 12 + z); ++ } ++ ++ public void setNeighborUnloaded(final int x, final int z) { ++ this.neighbors &= ~(0x1 << (x * 5 + 12 + z)); ++ } ++ // CraftBukkit end ++ + public Chunk(World worldIn, int x, int z) + { + this.storageArrays = new ExtendedBlockStorage[16]; +@@ -92,8 +127,13 @@ + Arrays.fill(this.precipitationHeightMap, -999); + Arrays.fill(this.blockBiomeArray, (byte) - 1); + capabilities = net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(this); ++ this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); ++ this.chunkKey = ChunkPos.asLong(this.x, this.z); + } + ++ public org.bukkit.Chunk bukkitChunk; ++ public boolean mustSave; ++ + public Chunk(World worldIn, ChunkPrimer primer, int x, int z) + { + this(worldIn, x, z); +@@ -271,6 +311,19 @@ + this.isGapLightingUpdated = true; + } + ++ /** ++ * PaperSpigot - Recheck gaps asynchronously. ++ */ ++ public void recheckGapsAsync(boolean isStatic) { ++ ++ world.lightingExecutor.submit(new Runnable() { ++ @Override ++ public void run() { ++ Chunk.this.recheckGaps(isStatic); ++ } ++ }); ++ } ++ + private void recheckGaps(boolean onlyOne) + { + this.world.profiler.startSection("recheckGaps"); +@@ -565,12 +618,12 @@ + { + if (block1 != block) //Only fire block breaks when the block changes. + block1.breakBlock(this.world, pos, iblockstate); +- TileEntity te = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); ++ TileEntity te = this.getTileEntity(pos, EnumCreateEntityType.CHECK); + if (te != null && te.shouldRefresh(this.world, pos, iblockstate, state)) this.world.removeTileEntity(pos); + } + else if (block1.hasTileEntity(iblockstate)) + { +- TileEntity te = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); ++ TileEntity te = this.getTileEntity(pos, EnumCreateEntityType.CHECK); + if (te != null && te.shouldRefresh(this.world, pos, iblockstate, state)) + this.world.removeTileEntity(pos); + } +@@ -608,6 +661,7 @@ + } + } + ++ // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. + // If capturing blocks, only run block physics for TE's. Non-TE's are handled in ForgeHooks.onPlaceItemIntoWorld + if (!this.world.isRemote && block1 != block && (!this.world.captureBlockSnapshots || block.hasTileEntity(state))) + { +@@ -616,7 +670,7 @@ + + if (block.hasTileEntity(state)) + { +- TileEntity tileentity1 = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); ++ TileEntity tileentity1 = this.getTileEntity(pos, EnumCreateEntityType.CHECK); + + if (tileentity1 == null) + { +@@ -743,6 +797,20 @@ + entityIn.chunkCoordZ = this.z; + this.entityLists[k].add(entityIn); + this.markDirty(); // Forge - ensure chunks are marked to save after an entity add ++ // Spigot start - increment creature type count ++ // Keep this synced up with World.a(Class) ++ if (entityIn instanceof EntityLiving) { ++ EntityLiving entityinsentient = (EntityLiving) entityIn; ++ if (entityinsentient.canDespawn() && entityinsentient.isNoDespawnRequired()) { ++ return; ++ } ++ } ++ for (EnumCreatureType creatureType : EnumCreatureType.values()) { ++ if (creatureType.getCreatureClass().isAssignableFrom(entityIn.getClass())) { ++ this.entityCount.adjustOrPutValue(creatureType.getCreatureClass(), 1, 1); ++ } ++ } ++ // Spigot end + } + + public void removeEntity(Entity entityIn) +@@ -764,6 +832,22 @@ + + this.entityLists[index].remove(entityIn); + this.markDirty(); // Forge - ensure chunks are marked to save after entity removals ++ // Spigot start - decrement creature type count ++ // Keep this synced up with World.a(Class) ++ if (entityIn instanceof EntityLiving) { ++ EntityLiving entityinsentient = (EntityLiving) entityIn; ++ if (entityinsentient.canDespawn() && entityinsentient.isNoDespawnRequired()) { ++ return; ++ } ++ } ++ for ( EnumCreatureType creatureType : EnumCreatureType.values() ) ++ { ++ if ( creatureType.getCreatureClass().isAssignableFrom( entityIn.getClass() ) ) ++ { ++ this.entityCount.adjustValue( creatureType.getCreatureClass(), -1 ); ++ } ++ } ++ // Spigot end + } + + public boolean canSeeSky(BlockPos pos) +@@ -783,9 +867,12 @@ + } + + @Nullable +- public TileEntity getTileEntity(BlockPos pos, Chunk.EnumCreateEntityType p_177424_2_) ++ public TileEntity getTileEntity(BlockPos pos, EnumCreateEntityType p_177424_2_) + { +- TileEntity tileentity = this.tileEntities.get(pos); ++ TileEntity tileentity = null; ++ if (tileentity == null) { ++ tileentity = this.tileEntities.get(pos); ++ } + + if (tileentity != null && tileentity.isInvalid()) + { +@@ -795,12 +882,12 @@ + + if (tileentity == null) + { +- if (p_177424_2_ == Chunk.EnumCreateEntityType.IMMEDIATE) ++ if (p_177424_2_ == EnumCreateEntityType.IMMEDIATE) + { + tileentity = this.createNewTileEntity(pos); + this.world.setTileEntity(pos, tileentity); + } +- else if (p_177424_2_ == Chunk.EnumCreateEntityType.QUEUED) ++ else if (p_177424_2_ == EnumCreateEntityType.QUEUED) + { + this.tileEntityPosQueue.add(pos.toImmutable()); + } +@@ -834,6 +921,11 @@ + + tileEntityIn.validate(); + this.tileEntities.put(pos, tileEntityIn); ++ } else { ++ System.out.println("Attempted to place a tile entity (" + tileEntityIn + ") at " + tileEntityIn.getPos().getX() + "," + tileEntityIn.getPos().getY() + "," + tileEntityIn.getPos().getZ() ++ + " (" + org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(getBlockState(pos).getBlock()) + ") where there was no entity tile!"); ++ System.out.println("Chunk coordinates: " + (this.x * 16) + "," + (this.z * 16)); ++ new Exception().printStackTrace(); + } + } + +@@ -864,7 +956,7 @@ + + public void onUnload() + { +- java.util.Arrays.stream(entityLists).forEach(multimap -> com.google.common.collect.Lists.newArrayList(multimap.getByClass(net.minecraft.entity.player.EntityPlayer.class)).forEach(player -> world.updateEntityWithOptionalForce(player, false))); // FORGE - Fix for MC-92916 ++ Arrays.stream(entityLists).forEach(multimap -> com.google.common.collect.Lists.newArrayList(multimap.getByClass(net.minecraft.entity.player.EntityPlayer.class)).forEach(player -> world.updateEntityWithOptionalForce(player, false))); // FORGE - Fix for MC-92916 + this.loaded = false; + + for (TileEntity tileentity : this.tileEntities.values()) +@@ -874,7 +966,14 @@ + + for (ClassInheritanceMultiMap classinheritancemultimap : this.entityLists) + { +- this.world.unloadEntities(classinheritancemultimap); ++ // Do not pass along players, as doing so can get them stuck outside of time. ++ // (which for example disables inventory icon updates and prevents block breaking) ++ this.world.unloadEntities( ++ classinheritancemultimap ++ .stream() ++ .filter(entity -> !(entity instanceof EntityPlayerMP)) ++ .collect(Collectors.toCollection(() -> new ClassInheritanceMultiMap<>(Entity.class))) ++ ); + } + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.ChunkEvent.Unload(this)); + } +@@ -955,7 +1054,7 @@ + return true; + } + +- return this.dirty; ++ return ((this.dirty || this.hasEntities) && this.world.getTotalWorldTime() >= this.lastSaveTime + MinecraftServer.getServerCB().autosavePeriod); + } + + public Random getRandomWithSeed(long seed) +@@ -1001,6 +1100,63 @@ + } + } + ++ public void populateCB(IChunkProvider chunkProvider, IChunkGenerator chunkGenrator, boolean newChunk) ++ { ++ Server server = world.getServer(); ++ if (server != null) { ++ /* ++ * If it's a new world, the first few chunks are generated inside ++ * the World constructor. We can't reliably alter that, so we have ++ * no way of creating a CraftWorld/CraftServer at that point. ++ */ ++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, newChunk)); ++ } ++ ++ // Update neighbor counts ++ for (int x = -2; x < 3; x++) { ++ for (int z = -2; z < 3; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ Chunk neighbor = getWorld().getChunkIfLoaded(this.x + x, this.z + z); ++ if (neighbor != null) { ++ neighbor.setNeighborLoaded(-x, -z); ++ setNeighborLoaded(x, z); ++ } ++ } ++ } ++ Chunk chunk = chunkProvider.getLoadedChunk(this.x, this.z - 1); ++ Chunk chunk1 = chunkProvider.getLoadedChunk(this.x + 1, this.z); ++ Chunk chunk2 = chunkProvider.getLoadedChunk(this.x, this.z + 1); ++ Chunk chunk3 = chunkProvider.getLoadedChunk(this.x - 1, this.z); ++ ++ if (chunk1 != null && chunk2 != null && chunkProvider.getLoadedChunk(this.x + 1, this.z + 1) != null) ++ { ++ this.populate(chunkGenrator); ++ } ++ ++ if (chunk3 != null && chunk2 != null && chunkProvider.getLoadedChunk(this.x - 1, this.z + 1) != null) ++ { ++ chunk3.populate(chunkGenrator); ++ } ++ ++ if (chunk != null && chunk1 != null && chunkProvider.getLoadedChunk(this.x + 1, this.z - 1) != null) ++ { ++ chunk.populate(chunkGenrator); ++ } ++ ++ if (chunk != null && chunk3 != null) ++ { ++ Chunk chunk4 = chunkProvider.getLoadedChunk(this.x - 1, this.z - 1); ++ ++ if (chunk4 != null) ++ { ++ chunk4.populate(chunkGenrator); ++ } ++ } ++ } ++ + protected void populate(IChunkGenerator generator) + { + if (populating != null && net.minecraftforge.common.ForgeModContainer.logCascadingWorldGeneration) logCascadingWorldGeneration(); +@@ -1017,6 +1173,26 @@ + { + this.checkLight(); + generator.populate(this.x, this.z); ++ BlockSand.fallInstantly = true; ++ Random random = new Random(); ++ random.setSeed(world.getSeed()); ++ long xRand = random.nextLong() / 2L * 2L + 1L; ++ long zRand = random.nextLong() / 2L * 2L + 1L; ++ random.setSeed((long) this.x * xRand + (long) this.z * zRand ^ world.getSeed()); ++ ++ org.bukkit.World world = this.world.getWorld(); ++ if (world != null) { ++ this.world.populating = true; ++ try { ++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { ++ populator.populate(world, random, bukkitChunk); ++ } ++ } finally { ++ this.world.populating = false; ++ } ++ } ++ BlockSand.fallInstantly = false; ++ this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); + net.minecraftforge.fml.common.registry.GameRegistry.generateWorld(this.x, this.z, this.world, generator, this.world.getChunkProvider()); + this.markDirty(); + } +@@ -1061,7 +1237,7 @@ + { + if (this.isGapLightingUpdated && this.world.provider.hasSkyLight() && !skipRecheckGaps) + { +- this.recheckGaps(this.world.isRemote); ++ this.recheckGapsAsync(this.world.isRemote); + } + + this.ticked = true; +@@ -1075,7 +1251,7 @@ + { + BlockPos blockpos = this.tileEntityPosQueue.poll(); + +- if (this.getTileEntity(blockpos, Chunk.EnumCreateEntityType.CHECK) == null && this.getBlockState(blockpos).getBlock().hasTileEntity(this.getBlockState(blockpos))) ++ if (this.getTileEntity(blockpos, EnumCreateEntityType.CHECK) == null && this.getBlockState(blockpos).getBlock().hasTileEntity(this.getBlockState(blockpos))) + { + TileEntity tileentity = this.createNewTileEntity(blockpos); + this.world.setTileEntity(blockpos, tileentity); +@@ -1086,7 +1262,7 @@ + + public boolean isPopulated() + { +- return this.ticked && this.isTerrainPopulated && this.isLightPopulated; ++ return !this.world.spigotConfig.randomLightUpdates || (this.ticked && this.isTerrainPopulated && this.isLightPopulated); + } + + public boolean wasTicked() +@@ -1336,7 +1512,7 @@ + this.updateSkylightColumns[i] = true; + } + +- this.recheckGaps(false); ++ this.recheckGapsAsync(false); + } + + private void checkLightSide(EnumFacing facing) diff --git a/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch b/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch new file mode 100644 index 00000000..a5f2d937 --- /dev/null +++ b/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch @@ -0,0 +1,65 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java +@@ -1,8 +1,6 @@ + package net.minecraft.world.chunk.storage; + + import com.google.common.collect.Maps; +-import java.io.DataInputStream; +-import java.io.DataOutputStream; + import java.io.File; + import java.io.IOException; + import java.util.Collections; +@@ -13,7 +11,6 @@ + import net.minecraft.block.Block; + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityList; +-import net.minecraft.nbt.CompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.tileentity.TileEntity; +@@ -79,14 +76,14 @@ + + if (nbttagcompound == null) + { +- DataInputStream datainputstream = RegionFileCache.getChunkInputStream(this.chunkSaveLocation, x, z); ++ NBTTagCompound nbtTagCompound = RegionFileCache.getChunkInputStreamCB(this.chunkSaveLocation, x, z); + +- if (datainputstream == null) ++ if (nbtTagCompound == null) + { + return null; + } + +- nbttagcompound = this.fixer.process(FixTypes.CHUNK, CompressedStreamTools.read(datainputstream)); ++ nbttagcompound = this.fixer.process(FixTypes.CHUNK, nbtTagCompound); + } + + return this.checkedReadChunkFromNBT__Async(worldIn, x, z, nbttagcompound); +@@ -237,9 +234,10 @@ + + private void writeChunkData(ChunkPos pos, NBTTagCompound compound) throws IOException + { +- DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, pos.x, pos.z); +- CompressedStreamTools.write(compound, dataoutputstream); +- dataoutputstream.close(); ++ // DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, pos.x, pos.z); ++ // CompressedStreamTools.write(compound, dataoutputstream); ++ // dataoutputstream.close(); ++ RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, pos.x, pos.z, compound); + } + + public void saveExtraChunkData(World worldIn, Chunk chunkIn) throws IOException +@@ -608,8 +606,11 @@ + + public static void spawnEntity(Entity entityIn, World worldIn) + { +- if (worldIn.spawnEntity(entityIn) && entityIn.isBeingRidden()) +- { ++ spawnEntity(entityIn, worldIn, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public static void spawnEntity(Entity entityIn, World worldIn, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ if (worldIn.spawnEntity(entityIn, reason) && entityIn.isBeingRidden()) { + for (Entity entity : entityIn.getPassengers()) + { + spawnEntity(entity, worldIn); diff --git a/patches/net/minecraft/world/chunk/storage/AnvilSaveHandler.java.patch b/patches/net/minecraft/world/chunk/storage/AnvilSaveHandler.java.patch new file mode 100644 index 00000000..6d6a8847 --- /dev/null +++ b/patches/net/minecraft/world/chunk/storage/AnvilSaveHandler.java.patch @@ -0,0 +1,25 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/storage/AnvilSaveHandler.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/storage/AnvilSaveHandler.java +@@ -21,8 +21,11 @@ + public IChunkLoader getChunkLoader(WorldProvider provider) + { + File file1 = this.getWorldDirectory(); ++ // To workaround the issue of Bukkit relying on every world having a seperate container ++ // we won't be generating a DIMXX folder for chunk loaders since this name is already generated ++ // for the world container with provider.getSaveFolder(). + +- if (provider.getSaveFolder() != null) ++ /*if (provider.getSaveFolder() != null) + { + File file3 = new File(file1, provider.getSaveFolder()); + file3.mkdirs(); +@@ -31,7 +34,8 @@ + else + { + return new AnvilChunkLoader(file1, this.dataFixer); +- } ++ }*/ ++ return new AnvilChunkLoader(file1,this.dataFixer); + } + + public void saveWorldInfoWithPlayer(WorldInfo worldInformation, @Nullable NBTTagCompound tagCompound) diff --git a/patches/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java.patch b/patches/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java.patch new file mode 100644 index 00000000..db3e7fcd --- /dev/null +++ b/patches/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java.patch @@ -0,0 +1,35 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java +@@ -27,6 +27,22 @@ + } + } + ++ public ExtendedBlockStorage(int y, boolean flag, char[] blockIds) { ++ this.yBase = y; ++ this.data = new BlockStateContainer(); ++ for (int i = 0; i < blockIds.length; i++) { ++ int xx = i & 15; ++ int yy = (i >> 8) & 15; ++ int zz = (i >> 4) & 15; ++ this.data.set(xx, yy, zz, Block.BLOCK_STATE_IDS.getByValue(blockIds[i])); ++ } ++ this.blockLight = new NibbleArray(); ++ if (flag) { ++ this.skyLight = new NibbleArray(); ++ } ++ recalculateRefCounts(); ++ } ++ + public IBlockState get(int x, int y, int z) + { + return this.data.get(x, y, z); +@@ -65,7 +81,8 @@ + + public boolean isEmpty() + { +- return this.blockRefCount == 0; ++ // return this.blockRefCount == 0; ++ return false; // CraftBukkit - MC-80966 + } + + public boolean needsRandomTick() diff --git a/patches/net/minecraft/world/chunk/storage/RegionFile.java.patch b/patches/net/minecraft/world/chunk/storage/RegionFile.java.patch new file mode 100644 index 00000000..f2a9476d --- /dev/null +++ b/patches/net/minecraft/world/chunk/storage/RegionFile.java.patch @@ -0,0 +1,399 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/storage/RegionFile.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/storage/RegionFile.java +@@ -1,29 +1,28 @@ + package net.minecraft.world.chunk.storage; + + import com.google.common.collect.Lists; +-import java.io.BufferedInputStream; +-import java.io.BufferedOutputStream; +-import java.io.ByteArrayInputStream; +-import java.io.ByteArrayOutputStream; +-import java.io.DataInputStream; +-import java.io.DataOutputStream; +-import java.io.File; +-import java.io.IOException; +-import java.io.RandomAccessFile; ++import java.io.*; ++import java.nio.ByteBuffer; ++import java.nio.IntBuffer; + import java.util.List; + import java.util.zip.DeflaterOutputStream; + import java.util.zip.GZIPInputStream; + import java.util.zip.InflaterInputStream; + import javax.annotation.Nullable; ++import net.minecraft.nbt.CompressedStreamTools; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; + + public class RegionFile + { +- // Minecraft is limited to 256 sections per chunk. So 1MB. This can easily be override. ++ // Spigot start ++ // Minecraft is limited to 256 sections per chunk. So 1MB. This can easily be overriden. + // So we extend this to use the REAL size when the count is maxed by seeking to that section and reading the length. +- private static final boolean FORGE_ENABLE_EXTENDED_SAVE = Boolean.parseBoolean(System.getProperty("forge.enableExtendedSave", "true")); ++ private static final boolean ENABLE_EXTENDED_SAVE = Boolean.parseBoolean(System.getProperty("net.minecraft.world.chunk.storage.RegionFile.enableExtendedSave", "true")); ++ // Spigot end + private static final byte[] EMPTY_SECTOR = new byte[4096]; + private final File fileName; ++ private File getFile() { return fileName; } + private RandomAccessFile dataFile; + private final int[] offsets = new int[1024]; + private final int[] chunkTimestamps = new int[1024]; +@@ -45,7 +44,7 @@ + + this.dataFile = new RandomAccessFile(fileNameIn, "rw"); + +- if (this.dataFile.length() < 4096L) ++ if (this.dataFile.length() < 8192L) // Paper - headers should be 8192 + { + this.dataFile.write(EMPTY_SECTOR); + this.dataFile.write(EMPTY_SECTOR); +@@ -72,36 +71,54 @@ + this.sectorFree.set(1, Boolean.valueOf(false)); + this.dataFile.seek(0L); + ++ int k; ++ ++ // Paper Start ++ ByteBuffer header = ByteBuffer.allocate(8192); ++ while (header.hasRemaining()) { ++ if (this.dataFile.getChannel().read(header) == -1) { ++ throw new EOFException(); ++ } ++ } ++ header.clear(); ++ IntBuffer headerAsInts = header.asIntBuffer(); ++ initOversizedState(); ++ // Paper End + for (int j1 = 0; j1 < 1024; ++j1) + { +- int k = this.dataFile.readInt(); ++ k = headerAsInts.get(); // Paper + this.offsets[j1] = k; +- ++ // Spigot start + int length = k & 255; + if (length == 255) + { +- if ((k >> 8) <= this.sectorFree.size()) +- { // We're maxed out, so we need to read the proper length from the section ++ if ((k >> 8) <= this.sectorFree.size()) { ++ // We're maxed out, so we need to read the proper length from the section + this.dataFile.seek((k >> 8) * 4096); + length = (this.dataFile.readInt() + 4)/ 4096 + 1; + this.dataFile.seek(j1 * 4 + 4); //Go back to where we were + } + } +- if (k != 0 && (k >> 8) + length <= this.sectorFree.size()) ++ if (k > 0 && (k >> 8) > 1 && (k >> 8) + (length) <= this.sectorFree.size()) // Paper >= 1 as 0/1 are the headers, and negative isnt valid + { +- for (int l = 0; l < length; ++l) ++ for (int l = 0; l < (length); ++l) + { ++ // Spigot end + this.sectorFree.set((k >> 8) + l, Boolean.valueOf(false)); + } + } +- else if (length > 0) +- net.minecraftforge.fml.common.FMLLog.log.warn("Invalid chunk: ({}, {}) Offset: {} Length: {} runs off end file. {}", j1 % 32, (int)(j1 / 32), k >> 8, length, fileNameIn); ++ // Spigot start ++ else if (k != 0) { // Paper ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Invalid chunk: ({0}, {1}) Offset: {2} Length: {3} runs off end file. {4}", new Object[]{j1 % 32, (int) (j1 / 32), k >> 8, length, fileNameIn}); ++ deleteChunk(j1); // Paper ++ } ++ // Spigot end + } + + for (int k1 = 0; k1 < 1024; ++k1) + { +- int l1 = this.dataFile.readInt(); +- this.chunkTimestamps[k1] = l1; ++ k = headerAsInts.get(); // Paper ++ if (offsets[k1] != 0) this.chunkTimestamps[k1] = k; // Paper - don't set timestamp if it got 0'd above due to corruption + } + } + catch (IOException ioexception) +@@ -138,11 +155,13 @@ + { + int j = i >> 8; + int k = i & 255; ++ // Spigot start + if (k == 255) + { + this.dataFile.seek(j * 4096); + k = (this.dataFile.readInt() + 4) / 4096 + 1; + } ++ // Spigot end + + if (j + k > this.sectorFree.size()) + { +@@ -155,12 +174,10 @@ + + if (l > 4096 * k) + { +- net.minecraftforge.fml.common.FMLLog.log.warn("Invalid chunk: ({}, {}) Offset: {} Invalid Size: {}>{} {}", x, z, j, l, k * 4096, fileName); + return null; + } + else if (l <= 0) + { +- net.minecraftforge.fml.common.FMLLog.log.warn("Invalid chunk: ({}, {}) Offset: {} Invalid Size: {} {}", x, z, j, l, fileName); + return null; + } + else +@@ -197,7 +214,7 @@ + @Nullable + public DataOutputStream getChunkDataOutputStream(int x, int z) + { +- return this.outOfBounds(x, z) ? null : new DataOutputStream(new BufferedOutputStream(new DeflaterOutputStream(new RegionFile.ChunkBuffer(x, z)))); ++ return this.outOfBounds(x, z) ? null : new DataOutputStream(new ChunkBuffer(x, z)); // Paper - remove middleware, move deflate to .close() for dynamic levels + } + + protected synchronized void write(int x, int z, byte[] data, int length) +@@ -207,17 +224,21 @@ + int i = this.getOffset(x, z); + int j = i >> 8; + int k = i & 255; ++ // Spigot start + if (k == 255) + { + this.dataFile.seek(j * 4096); + k = (this.dataFile.readInt() + 4) / 4096 + 1; + } ++ // Spigot end + int l = (length + 5) / 4096 + 1; + + if (l >= 256) + { +- if (!FORGE_ENABLE_EXTENDED_SAVE) return; +- net.minecraftforge.fml.common.FMLLog.log.warn("Large Chunk Detected: ({}, {}) Size: {} {}", x, z, l, fileName); ++ // Spigot start ++ if (!ENABLE_EXTENDED_SAVE) return; ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING,"Large Chunk Detected: ({0}, {1}) Size: {2} {3}", new Object[]{x, z, l, this.fileName}); ++ // Spigot end + } + + if (j != 0 && k == l) +@@ -265,7 +286,7 @@ + if (j1 >= l) + { + j = l1; +- this.setOffset(x, z, l1 << 8 | (l > 255 ? 255 : l)); ++ this.setOffset(x, z, l1 << 8 | (l > 255 ? 255 : l)); // Spigot + + for (int j2 = 0; j2 < l; ++j2) + { +@@ -287,7 +308,7 @@ + + this.sizeDelta += 4096 * l; + this.write(j, data, length); +- this.setOffset(x, z, j << 8 | (l > 255 ? 255 : l)); ++ this.setOffset(x, z, j << 8 | (l > 255 ? 255 : l)); // Spigot + } + } + +@@ -295,7 +316,7 @@ + } + catch (IOException ioexception) + { +- ioexception.printStackTrace(); ++ org.spigotmc.SneakyThrow.sneaky(ioexception); // Paper - we want the upper try/catch to retry this + } + } + +@@ -344,6 +365,159 @@ + } + } + ++ // Paper start ++ public synchronized void deleteChunk(int j1) { ++ backup(); ++ int k = offsets[j1]; ++ int x = j1 & 1024; ++ int z = j1 >> 2; ++ int offset = (k >> 8); ++ int len = (k & 255); ++ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(); ++ String debug = "idx:" + + j1 + " - " + x + "," + z + " - offset: " + offset + " - len: " + len; ++ try { ++ RandomAccessFile file = dataFile; ++ file.seek(j1 * 4); ++ file.writeInt(0); ++ // clear the timestamp ++ file.seek(4096 + j1 * 4); ++ file.writeInt(0); ++ chunkTimestamps[j1] = 0; ++ offsets[j1] = 0; ++ logger.error("Deleted corrupt chunk (" + debug + ") " + getFile().getAbsolutePath()); ++ } catch (IOException e) { ++ ++ logger.error("Error deleting corrupt chunk (" + debug + ") " + getFile().getAbsolutePath(), e); ++ } ++ } ++ private boolean backedUp = false; ++ private synchronized void backup() { ++ if (backedUp) { ++ return; ++ } ++ backedUp = true; ++ File file = this.getFile(); ++ java.text.DateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd"); ++ java.util.Date today = new java.util.Date(); ++ File corrupt = new File(file.getParentFile(), file.getName() + "." + formatter.format(today) + ".corrupt"); ++ if (corrupt.exists()) { ++ return; ++ } ++ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(); ++ logger.error("Region file " + file.getAbsolutePath() + " was corrupt. Backing up to " + corrupt.getAbsolutePath() + " and repairing"); ++ try { ++ java.nio.file.Files.copy(file.toPath(), corrupt.toPath()); ++ ++ } catch (IOException e) { ++ logger.error("Error backing up corrupt file" + file.getAbsolutePath(), e); ++ } ++ } ++ ++ private final byte[] oversized = new byte[1024]; ++ private int oversizedCount = 0; ++ ++ private synchronized void initOversizedState() throws IOException { ++ File metaFile = getOversizedMetaFile(); ++ if (metaFile.exists()) { ++ final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath()); ++ System.arraycopy(read, 0, oversized, 0, oversized.length); ++ for (byte temp : oversized) { ++ oversizedCount += temp; ++ } ++ } ++ } ++ ++ private static int getChunkIndex(int x, int z) { ++ return (x & 31) + (z & 31) * 32; ++ } ++ synchronized boolean isOversized(int x, int z) { ++ return this.oversized[getChunkIndex(x, z)] == 1; ++ } ++ synchronized void setOversized(int x, int z, boolean oversized) throws IOException { ++ final int offset = getChunkIndex(x, z); ++ boolean previous = this.oversized[offset] == 1; ++ this.oversized[offset] = (byte) (oversized ? 1 : 0); ++ if (!previous && oversized) { ++ oversizedCount++; ++ } else if (!oversized && previous) { ++ oversizedCount--; ++ } ++ if (previous && !oversized) { ++ File oversizedFile = getOversizedFile(x, z); ++ if (oversizedFile.exists()) { ++ oversizedFile.delete(); ++ } ++ } ++ if (oversizedCount > 0) { ++ if (previous != oversized) { ++ writeOversizedMeta(); ++ } ++ } else if (previous) { ++ File oversizedMetaFile = getOversizedMetaFile(); ++ if (oversizedMetaFile.exists()) { ++ oversizedMetaFile.delete(); ++ } ++ } ++ } ++ ++ private void writeOversizedMeta() throws IOException { ++ java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized); ++ } ++ ++ private File getOversizedMetaFile() { ++ return new File(getFile().getParentFile(), getFile().getName().replaceAll("\\.mca$", "") + ".oversized.nbt"); ++ } ++ ++ private File getOversizedFile(int x, int z) { ++ return new File(this.getFile().getParentFile(), this.getFile().getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); ++ } ++ ++ void writeOversizedData(int x, int z, NBTTagCompound oversizedData) throws IOException { ++ File file = getOversizedFile(x, z); ++ try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new DeflaterOutputStream(new java.io.FileOutputStream(file), new java.util.zip.Deflater(java.util.zip.Deflater.BEST_COMPRESSION), 32 * 1024), 32 * 1024))) { ++ CompressedStreamTools.write(oversizedData, out); ++ } ++ this.setOversized(x, z, true); ++ ++ } ++ ++ synchronized NBTTagCompound getOversizedData(int x, int z) throws IOException { ++ File file = getOversizedFile(x, z); ++ try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) { ++ return CompressedStreamTools.read(out); ++ } ++ ++ } ++ ++ private static final boolean USE_SPIGOT_OVERSIZED_METHOD = Boolean.getBoolean("Paper.useSpigotExtendedSaveMethod"); // Paper ++ static { ++ if (USE_SPIGOT_OVERSIZED_METHOD) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "===================================="); ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Using Spigot Oversized Chunk save method. Warning this will result in extremely fragmented chunks, as well as making the entire region file unable to be to used in any other software but Forge or Spigot (not usable in Vanilla or CraftBukkit). Paper's method is highly recommended."); ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "===================================="); ++ } ++ } ++ ++ public class ChunkTooLargeException extends RuntimeException { ++ public ChunkTooLargeException(int x, int z, int sectors) { ++ super("Chunk " + x + "," + z + " of " + getFile().toString() + " is too large (" + sectors + "/256)"); ++ } ++ } ++ private static class DirectByteArrayOutputStream extends ByteArrayOutputStream { ++ public DirectByteArrayOutputStream() { ++ super(); ++ } ++ ++ public DirectByteArrayOutputStream(int size) { ++ super(size); ++ } ++ ++ public byte[] getBuffer() { ++ return this.buf; ++ } ++ } ++ // Paper end ++ + class ChunkBuffer extends ByteArrayOutputStream + { + private final int chunkX; +@@ -358,7 +532,34 @@ + + public void close() throws IOException + { +- RegionFile.this.write(this.chunkX, this.chunkZ, this.buf, this.count); ++ // Paper start - apply dynamic compression ++ int origLength = this.count; ++ byte[] buf = this.buf; ++ DirectByteArrayOutputStream out = compressData(buf, origLength); ++ byte[] bytes = out.getBuffer(); ++ int length = out.size(); ++ ++ RegionFile.this.write(this.chunkX, this.chunkZ, bytes, length); + } ++ } ++ ++ private static final byte[] compressionBuffer = new byte[1024 * 64]; // 64k fits most standard chunks input size even, ideally 1 pass through zlib ++ private static final java.util.zip.Deflater deflater = new java.util.zip.Deflater(); ++ // since file IO is single threaded, no benefit to using per-region file buffers/synchronization, we can change that later if it becomes viable. ++ private static DirectByteArrayOutputStream compressData(byte[] buf, int length) throws IOException { ++ synchronized (deflater) { ++ deflater.setInput(buf, 0, length); ++ deflater.finish(); ++ ++ ++ DirectByteArrayOutputStream out = new DirectByteArrayOutputStream(length); ++ while (!deflater.finished()) { ++ out.write(compressionBuffer, 0, deflater.deflate(compressionBuffer)); ++ } ++ out.close(); ++ deflater.reset(); ++ return out; + } + } ++ // Paper end ++} diff --git a/patches/net/minecraft/world/chunk/storage/RegionFileCache.java.patch b/patches/net/minecraft/world/chunk/storage/RegionFileCache.java.patch new file mode 100644 index 00000000..cb9e02cf --- /dev/null +++ b/patches/net/minecraft/world/chunk/storage/RegionFileCache.java.patch @@ -0,0 +1,232 @@ +--- ../src-base/minecraft/net/minecraft/world/chunk/storage/RegionFileCache.java ++++ ../src-work/minecraft/net/minecraft/world/chunk/storage/RegionFileCache.java +@@ -1,15 +1,24 @@ + package net.minecraft.world.chunk.storage; + + import com.google.common.collect.Maps; ++import java.util.Iterator; ++import java.util.LinkedHashMap; ++import java.util.Map.Entry; ++import net.minecraft.nbt.CompressedStreamTools; ++import net.minecraft.nbt.NBTBase; ++import net.minecraft.nbt.NBTTagCompound; ++ ++import javax.annotation.Nullable; + import java.io.DataInputStream; + import java.io.DataOutputStream; + import java.io.File; + import java.io.IOException; + import java.util.Map; ++import net.minecraft.nbt.NBTTagList; + + public class RegionFileCache + { +- private static final Map REGIONS_BY_FILE = Maps.newHashMap(); ++ public static final Map REGIONS_BY_FILE = new LinkedHashMap<>(256, 0.75f, true); // Spigot - private -> public, Paper - HashMap -> LinkedHashMap + + public static synchronized RegionFile createOrLoadRegionFile(File worldDir, int chunkX, int chunkZ) + { +@@ -30,7 +39,7 @@ + + if (REGIONS_BY_FILE.size() >= 256) + { +- clearRegionFileReferences(); ++ trimCache(); // Paper + } + + RegionFile regionfile1 = new RegionFile(file2); +@@ -38,7 +47,154 @@ + return regionfile1; + } + } ++ // Paper Start ++ private static synchronized void trimCache() { ++ Iterator> itr = RegionFileCache.REGIONS_BY_FILE.entrySet().iterator(); ++ int count = RegionFileCache.REGIONS_BY_FILE.size() - 256; ++ while (count-- >= 0 && itr.hasNext()) { ++ try { ++ itr.next().getValue().close(); ++ } catch (IOException ioexception) { ++ ioexception.printStackTrace(); ++ } ++ itr.remove(); ++ } ++ } + ++ private static void printOversizedLog(String msg, File file, int x, int z) { ++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); ++ } ++ ++ private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8; ++ private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64; ++ private static final int OVERZEALOUS_THRESHOLD = 1024; ++ private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD; ++ private static void resetFilterThresholds() { ++ SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD)); ++ } ++ static { ++ resetFilterThresholds(); ++ } ++ ++ static boolean isOverzealous() { ++ return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD; ++ } ++ ++ private static void writeRegion(File file, int x, int z, NBTTagCompound nbttagcompound) throws IOException { ++ RegionFile regionfile = createOrLoadRegionFile(file, x, z); ++ ++ DataOutputStream out = regionfile.getChunkDataOutputStream(x & 31, z & 31); ++ try { ++ CompressedStreamTools.write(nbttagcompound, out); ++ out.close(); ++ regionfile.setOversized(x, z, false); ++ } catch (RegionFile.ChunkTooLargeException ignored) { ++ printOversizedLog("ChunkTooLarge! Someone is trying to duplicate.", file, x, z); ++ // Clone as we are now modifying it, don't want to corrupt the pending save state ++ nbttagcompound = (NBTTagCompound) nbttagcompound.copy(); ++ // Filter out TileEntities and Entities ++ NBTTagCompound oversizedData = filterChunkData(nbttagcompound); ++ //noinspection SynchronizationOnLocalVariableOrMethodParameter ++ synchronized (regionfile) { ++ out = regionfile.getChunkDataOutputStream(x & 31, z & 31); ++ CompressedStreamTools.write(nbttagcompound, out); ++ try { ++ out.close(); ++ // 2048 is below the min allowed, so it means we enter overzealous mode below ++ if (SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD) { ++ resetFilterThresholds(); ++ } ++ } catch (RegionFile.ChunkTooLargeException e) { ++ printOversizedLog("ChunkTooLarge even after reduction. Trying in overzealous mode.", file, x, z); ++ // Eek, major fail. We have retry logic, so reduce threshholds and fall back ++ SIZE_THRESHOLD = OVERZEALOUS_THRESHOLD; ++ throw e; ++ } ++ ++ regionfile.writeOversizedData(x, z, oversizedData); ++ } ++ } catch (Exception e) { ++ e.printStackTrace(); ++ throw e; ++ } ++ ++ } ++ ++ private static NBTTagCompound filterChunkData(NBTTagCompound chunk) { ++ NBTTagCompound oversizedLevel = new NBTTagCompound(); ++ NBTTagCompound level = chunk.getCompoundTag("Level"); ++ filterChunkList(level, oversizedLevel, "Entities"); ++ filterChunkList(level, oversizedLevel, "TileEntities"); ++ NBTTagCompound oversized = new NBTTagCompound(); ++ oversized.setTag("Level", oversizedLevel); ++ return oversized; ++ } ++ ++ private static void filterChunkList(NBTTagCompound level, NBTTagCompound extra, String key) { ++ NBTTagList list = level.getTagList(key, 10); ++ NBTTagList newList = extra.getTagList(key, 10); ++ int totalSize = 0; ++ for (Iterator iterator = list.tagList.iterator(); iterator.hasNext(); ) { ++ NBTBase object = iterator.next(); ++ int nbtSize = getNBTSize(object); ++ if (nbtSize > SIZE_THRESHOLD || (SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD && totalSize > OVERZEALOUS_TOTAL_THRESHOLD)) { ++ newList.appendTag(object); ++ iterator.remove(); ++ } else { ++ totalSize += nbtSize; ++ } ++ } ++ level.setTag(key, list); ++ extra.setTag(key, newList); ++ } ++ ++ ++ private static NBTTagCompound readOversizedChunk(RegionFile regionfile, int i, int j) throws IOException { ++ synchronized (regionfile) { ++ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(i & 31, j & 31)) { ++ NBTTagCompound oversizedData = regionfile.getOversizedData(i, j); ++ NBTTagCompound chunk = CompressedStreamTools.read(datainputstream); ++ if (oversizedData == null) { ++ return chunk; ++ } ++ NBTTagCompound oversizedLevel = oversizedData.getCompoundTag("Level"); ++ NBTTagCompound level = chunk.getCompoundTag("Level"); ++ ++ mergeChunkList(level, oversizedLevel, "Entities"); ++ mergeChunkList(level, oversizedLevel, "TileEntities"); ++ ++ chunk.setTag("Level", level); ++ ++ return chunk; ++ } catch (Throwable throwable) { ++ throwable.printStackTrace(); ++ throw throwable; ++ } ++ } ++ } ++ ++ private static void mergeChunkList(NBTTagCompound level, NBTTagCompound oversizedLevel, String key) { ++ NBTTagList levelList = level.getTagList(key, 10); ++ NBTTagList oversizedList = oversizedLevel.getTagList(key, 10); ++ ++ if (!oversizedList.hasNoTags()) { ++ oversizedList.tagList.forEach(levelList::appendTag); ++ level.setTag(key, levelList); ++ } ++ } ++ ++ private static int getNBTSize(NBTBase nbtBase) { ++ DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream()); ++ try { ++ nbtBase.write(test); ++ return test.size(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return 0; ++ } ++ } ++ // Paper End ++ + public static synchronized RegionFile getRegionFileIfExists(File worldDir, int chunkX, int chunkZ) + { + File file1 = new File(worldDir, "region"); +@@ -92,14 +248,38 @@ + return regionfile.getChunkDataInputStream(chunkX & 31, chunkZ & 31); + } + ++ @Nullable ++ public static NBTTagCompound getChunkInputStreamCB(File worldDir, int chunkX, int chunkZ) throws IOException ++ { // Paper - remove synchronization ++ RegionFile regionfile = createOrLoadRegionFile(worldDir, chunkX, chunkZ); ++ // Paper start ++ if (regionfile.isOversized(chunkX, chunkZ)) { ++ printOversizedLog("Loading Oversized Chunk!", worldDir, chunkX, chunkZ); ++ return readOversizedChunk(regionfile, chunkX, chunkZ); ++ } ++ // Paper end ++ DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkX & 31, chunkZ & 31); ++ ++ if (datainputstream == null) { ++ return null; ++ } ++ ++ return CompressedStreamTools.read(datainputstream); ++ } ++ + public static DataOutputStream getChunkOutputStream(File worldDir, int chunkX, int chunkZ) + { + RegionFile regionfile = createOrLoadRegionFile(worldDir, chunkX, chunkZ); + return regionfile.getChunkDataOutputStream(chunkX & 31, chunkZ & 31); + } + ++ public static void getChunkOutputStream(File worldDir, int chunkX, int chunkZ, NBTTagCompound nbttagcompound) throws IOException ++ { // Paper - remove synchronization ++ writeRegion(worldDir, chunkX, chunkZ, nbttagcompound); // Paper - moved to own method ++ } ++ + public static boolean chunkExists(File worldDir, int chunkX, int chunkZ) +- { ++ { // Paper - remove synchronization + RegionFile regionfile = getRegionFileIfExists(worldDir, chunkX, chunkZ); + return regionfile != null ? regionfile.isChunkSaved(chunkX & 31, chunkZ & 31) : false; + } diff --git a/patches/net/minecraft/world/gen/ChunkGeneratorOverworld.java.patch b/patches/net/minecraft/world/gen/ChunkGeneratorOverworld.java.patch new file mode 100644 index 00000000..12b9f23b --- /dev/null +++ b/patches/net/minecraft/world/gen/ChunkGeneratorOverworld.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/ChunkGeneratorOverworld.java ++++ ../src-work/minecraft/net/minecraft/world/gen/ChunkGeneratorOverworld.java +@@ -296,6 +296,12 @@ + f6 = 1.0F + f6 * 4.0F; + } + ++ // CraftBukkit start - fix MC-54738 ++ if (f5 < -1.8F) { ++ f5 = -1.8F; ++ } ++ // CraftBukkit end ++ + float f7 = this.biomeWeights[j1 + 2 + (k1 + 2) * 5] / (f5 + 2.0F); + + if (biome1.getBaseHeight() > biome.getBaseHeight()) diff --git a/patches/net/minecraft/world/gen/ChunkProviderServer.java.patch b/patches/net/minecraft/world/gen/ChunkProviderServer.java.patch new file mode 100644 index 00000000..3a5c333b --- /dev/null +++ b/patches/net/minecraft/world/gen/ChunkProviderServer.java.patch @@ -0,0 +1,117 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/ChunkProviderServer.java ++++ ../src-work/minecraft/net/minecraft/world/gen/ChunkProviderServer.java +@@ -26,16 +26,17 @@ + import net.minecraft.world.chunk.storage.IChunkLoader; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.event.world.ChunkUnloadEvent; + + public class ChunkProviderServer implements IChunkProvider + { + private static final Logger LOGGER = LogManager.getLogger(); +- private final Set droppedChunksSet = Sets.newHashSet(); ++ public final Set droppedChunksSet = Sets.newHashSet(); + public final IChunkGenerator chunkGenerator; + public final IChunkLoader chunkLoader; + public final Long2ObjectMap id2ChunkMap = new Long2ObjectOpenHashMap(8192); + public final WorldServer world; +- private final Set loadingChunks = com.google.common.collect.Sets.newHashSet(); ++ private final Set loadingChunks = Sets.newHashSet(); + + public ChunkProviderServer(WorldServer worldObjIn, IChunkLoader chunkLoaderIn, IChunkGenerator chunkGeneratorIn) + { +@@ -83,6 +84,10 @@ + return chunk; + } + ++ public Chunk getChunkIfLoaded(int x, int z) { ++ return id2ChunkMap.get(ChunkPos.asLong(x, z)); ++ } ++ + @Nullable + public Chunk loadChunk(int x, int z) + { +@@ -106,7 +111,7 @@ + { + this.id2ChunkMap.put(ChunkPos.asLong(x, z), chunk); + chunk.onLoad(); +- chunk.populate(this, this.chunkGenerator); ++ chunk.populateCB(this, this.chunkGenerator, false); + } + + loadingChunks.remove(pos); +@@ -154,7 +159,7 @@ + + this.id2ChunkMap.put(i, chunk); + chunk.onLoad(); +- chunk.populate(this, this.chunkGenerator); ++ chunk.populateCB(this, this.chunkGenerator, true); + } + + return chunk; +@@ -266,17 +271,21 @@ + + if (chunk != null && chunk.unloadQueued) + { +- chunk.onUnload(); +- net.minecraftforge.common.ForgeChunkManager.putDormantChunk(ChunkPos.asLong(chunk.x, chunk.z), chunk); +- this.saveChunkData(chunk); +- this.saveChunkExtraData(chunk); +- this.id2ChunkMap.remove(olong); ++// chunk.onUnload(); ++// net.minecraftforge.common.ForgeChunkManager.putDormantChunk(ChunkPos.asLong(chunk.x, chunk.z), chunk); ++// this.saveChunkData(chunk); ++// this.saveChunkExtraData(chunk); ++// this.id2ChunkMap.remove(olong); ++ if (!unloadChunk(chunk, true)) { ++ continue; ++ } + ++i; + } + } + } + +- if (this.id2ChunkMap.isEmpty()) net.minecraftforge.common.DimensionManager.unloadWorld(this.world.provider.getDimension()); ++ // Kettle - Stops forge automatically unloading world on startup. ++// if (this.id2ChunkMap.isEmpty()) net.minecraftforge.common.DimensionManager.unloadWorld(this.world.provider.getDimension()); + + this.chunkLoader.chunkTick(); + } +@@ -284,6 +293,37 @@ + return false; + } + ++ public boolean unloadChunk(Chunk chunk, boolean save) { ++ ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk, save); ++ this.world.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ save = event.isSaveChunk(); ++ ++ // Update neighbor counts ++ for (int x = -2; x < 3; x++) { ++ for (int z = -2; z < 3; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ Chunk neighbor = this.getChunkIfLoaded(chunk.x + x, chunk.z + z); ++ if (neighbor != null) { ++ neighbor.setNeighborUnloaded(-x, -z); ++ chunk.setNeighborUnloaded(x, z); ++ } ++ } ++ } ++ // Moved from unloadChunks above ++ chunk.onUnload(); ++ net.minecraftforge.common.ForgeChunkManager.putDormantChunk(ChunkPos.asLong(chunk.x, chunk.z), chunk); ++ this.saveChunkData(chunk); ++ this.saveChunkExtraData(chunk); ++ this.id2ChunkMap.remove(chunk.chunkKey); ++ return true; ++ } ++ + public boolean canSave() + { + return !this.world.disableLevelSaving; diff --git a/patches/net/minecraft/world/gen/feature/WorldGenHugeTrees.java.patch b/patches/net/minecraft/world/gen/feature/WorldGenHugeTrees.java.patch new file mode 100644 index 00000000..cc5bfd92 --- /dev/null +++ b/patches/net/minecraft/world/gen/feature/WorldGenHugeTrees.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/feature/WorldGenHugeTrees.java ++++ ../src-work/minecraft/net/minecraft/world/gen/feature/WorldGenHugeTrees.java +@@ -1,8 +1,6 @@ + package net.minecraft.world.gen.feature; + + import java.util.Random; +-import net.minecraft.block.Block; +-import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.init.Blocks; + import net.minecraft.util.math.BlockPos; +@@ -59,7 +57,7 @@ + { + for (int l = -j; l <= j && flag; ++l) + { +- if (leavesPos.getY() + i < 0 || leavesPos.getY() + i >= 256 || !this.isReplaceable(worldIn,leavesPos.add(k, i, l))) ++ if (leavesPos.getY() + i < 0 || leavesPos.getY() + i >= 256 || (!this.isReplaceable(worldIn,leavesPos.add(k, i, l)) && worldIn.getBlockState(leavesPos.add(k, i, l)).getBlock() != Blocks.SAPLING)) // CraftBukkit - ignore our own saplings + { + flag = false; + } diff --git a/patches/net/minecraft/world/gen/feature/WorldGenShrub.java.patch b/patches/net/minecraft/world/gen/feature/WorldGenShrub.java.patch new file mode 100644 index 00000000..dbd1d995 --- /dev/null +++ b/patches/net/minecraft/world/gen/feature/WorldGenShrub.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/feature/WorldGenShrub.java ++++ ../src-work/minecraft/net/minecraft/world/gen/feature/WorldGenShrub.java +@@ -1,8 +1,6 @@ + package net.minecraft.world.gen.feature; + + import java.util.Random; +-import net.minecraft.block.Block; +-import net.minecraft.block.material.Material; + import net.minecraft.block.state.IBlockState; + import net.minecraft.init.Blocks; + import net.minecraft.util.math.BlockPos; +@@ -60,6 +58,9 @@ + } + } + } ++ // CraftBukkit start - Return false if gen was unsuccessful ++ } else { ++ return false; + } + + return true; diff --git a/patches/net/minecraft/world/gen/layer/IntCache.java.patch b/patches/net/minecraft/world/gen/layer/IntCache.java.patch new file mode 100644 index 00000000..9570946d --- /dev/null +++ b/patches/net/minecraft/world/gen/layer/IntCache.java.patch @@ -0,0 +1,41 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/layer/IntCache.java ++++ ../src-work/minecraft/net/minecraft/world/gen/layer/IntCache.java +@@ -18,13 +18,13 @@ + if (freeSmallArrays.isEmpty()) + { + int[] aint4 = new int[256]; +- inUseSmallArrays.add(aint4); ++ if (inUseSmallArrays.size() < org.spigotmc.SpigotConfig.intCacheLimit) inUseSmallArrays.add(aint4); + return aint4; + } + else + { + int[] aint3 = freeSmallArrays.remove(freeSmallArrays.size() - 1); +- inUseSmallArrays.add(aint3); ++ if (inUseSmallArrays.size() < org.spigotmc.SpigotConfig.intCacheLimit) inUseSmallArrays.add(aint3); + return aint3; + } + } +@@ -34,19 +34,19 @@ + freeLargeArrays.clear(); + inUseLargeArrays.clear(); + int[] aint2 = new int[intCacheSize]; +- inUseLargeArrays.add(aint2); ++ if (inUseLargeArrays.size() < org.spigotmc.SpigotConfig.intCacheLimit) inUseLargeArrays.add(aint2); + return aint2; + } + else if (freeLargeArrays.isEmpty()) + { + int[] aint1 = new int[intCacheSize]; +- inUseLargeArrays.add(aint1); ++ if (inUseLargeArrays.size() < org.spigotmc.SpigotConfig.intCacheLimit) inUseLargeArrays.add(aint1); + return aint1; + } + else + { + int[] aint = freeLargeArrays.remove(freeLargeArrays.size() - 1); +- inUseLargeArrays.add(aint); ++ if (inUseLargeArrays.size() < org.spigotmc.SpigotConfig.intCacheLimit) inUseLargeArrays.add(aint); + return aint; + } + } diff --git a/patches/net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces.java.patch b/patches/net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces.java.patch new file mode 100644 index 00000000..83034771 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces.java +@@ -773,7 +773,7 @@ + entitywitch.enablePersistence(); + entitywitch.setLocationAndAngles((double)l + 0.5D, (double)i1, (double)k + 0.5D, 0.0F, 0.0F); + entitywitch.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(l, i1, k)), (IEntityLivingData)null); +- worldIn.spawnEntity(entitywitch); ++ worldIn.spawnEntity(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); + } + } + diff --git a/patches/net/minecraft/world/gen/structure/MapGenScatteredFeature.java.patch b/patches/net/minecraft/world/gen/structure/MapGenScatteredFeature.java.patch new file mode 100644 index 00000000..2b749cb3 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/MapGenScatteredFeature.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/MapGenScatteredFeature.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/MapGenScatteredFeature.java +@@ -63,7 +63,7 @@ + + int k = chunkX / this.maxDistanceBetweenScatteredFeatures; + int l = chunkZ / this.maxDistanceBetweenScatteredFeatures; +- Random random = this.world.setRandomSeed(k, l, 14357617); ++ Random random = this.world.setRandomSeed(k, l, this.world.spigotConfig.largeFeatureSeed); + k = k * this.maxDistanceBetweenScatteredFeatures; + l = l * this.maxDistanceBetweenScatteredFeatures; + k = k + random.nextInt(this.maxDistanceBetweenScatteredFeatures - 8); diff --git a/patches/net/minecraft/world/gen/structure/MapGenStructure.java.patch b/patches/net/minecraft/world/gen/structure/MapGenStructure.java.patch new file mode 100644 index 00000000..2987b2a7 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/MapGenStructure.java.patch @@ -0,0 +1,17 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/MapGenStructure.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/MapGenStructure.java +@@ -175,8 +175,13 @@ + { + if (this.structureData == null && worldIn != null) + { ++ // Spigot Start ++ if (worldIn.spigotConfig.saveStructureInfo && !this.getStructureName().equals("Mineshaft")) { // // Cauldron + this.structureData = (MapGenStructureData)worldIn.getPerWorldStorage().getOrLoadData(MapGenStructureData.class, this.getStructureName()); +- ++ } else { ++ this.structureData = new MapGenStructureData(this.getStructureName()); ++ } ++ // Spigot End + if (this.structureData == null) + { + this.structureData = new MapGenStructureData(this.getStructureName()); diff --git a/patches/net/minecraft/world/gen/structure/MapGenVillage.java.patch b/patches/net/minecraft/world/gen/structure/MapGenVillage.java.patch new file mode 100644 index 00000000..93be5603 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/MapGenVillage.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/MapGenVillage.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/MapGenVillage.java +@@ -64,7 +64,7 @@ + + int k = chunkX / this.distance; + int l = chunkZ / this.distance; +- Random random = this.world.setRandomSeed(k, l, 10387312); ++ Random random = this.world.setRandomSeed(k, l, this.world.spigotConfig.villageSeed); + k = k * this.distance; + l = l * this.distance; + k = k + random.nextInt(this.distance - 8); diff --git a/patches/net/minecraft/world/gen/structure/StructureOceanMonument.java.patch b/patches/net/minecraft/world/gen/structure/StructureOceanMonument.java.patch new file mode 100644 index 00000000..fab1d7d5 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/StructureOceanMonument.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/StructureOceanMonument.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/StructureOceanMonument.java +@@ -72,7 +72,7 @@ + + int k = chunkX / this.spacing; + int l = chunkZ / this.spacing; +- Random random = this.world.setRandomSeed(k, l, 10387313); ++ Random random = this.world.setRandomSeed(k, l, this.world.spigotConfig.monumentSeed); + k = k * this.spacing; + l = l * this.spacing; + k = k + (random.nextInt(this.spacing - this.separation) + random.nextInt(this.spacing - this.separation)) / 2; diff --git a/patches/net/minecraft/world/gen/structure/StructureVillagePieces.java.patch b/patches/net/minecraft/world/gen/structure/StructureVillagePieces.java.patch new file mode 100644 index 00000000..de4a7bf3 --- /dev/null +++ b/patches/net/minecraft/world/gen/structure/StructureVillagePieces.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraft/world/gen/structure/StructureVillagePieces.java ++++ ../src-work/minecraft/net/minecraft/world/gen/structure/StructureVillagePieces.java +@@ -1782,7 +1782,7 @@ + entityzombievillager.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityzombievillager)), (IEntityLivingData)null); + entityzombievillager.setForgeProfession(this.chooseForgeProfession(i, net.minecraftforge.fml.common.registry.VillagerRegistry.FARMER)); + entityzombievillager.enablePersistence(); +- worldIn.spawnEntity(entityzombievillager); ++ worldIn.spawnEntity(entityzombievillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); + } + else + { +@@ -1791,7 +1791,7 @@ + net.minecraftforge.fml.common.registry.VillagerRegistry.setRandomProfession(entityvillager, worldIn.rand); + entityvillager.setProfession(this.chooseForgeProfession(i, entityvillager.getProfessionForge())); + entityvillager.finalizeMobSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityvillager)), (IEntityLivingData)null, false); +- worldIn.spawnEntity(entityvillager); ++ worldIn.spawnEntity(entityvillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); + } + } + } diff --git a/patches/net/minecraft/world/storage/ISaveHandler.java.patch b/patches/net/minecraft/world/storage/ISaveHandler.java.patch new file mode 100644 index 00000000..c560a3bb --- /dev/null +++ b/patches/net/minecraft/world/storage/ISaveHandler.java.patch @@ -0,0 +1,9 @@ +--- ../src-base/minecraft/net/minecraft/world/storage/ISaveHandler.java ++++ ../src-work/minecraft/net/minecraft/world/storage/ISaveHandler.java +@@ -30,4 +30,6 @@ + File getMapFileFromName(String mapName); + + TemplateManager getStructureTemplateManager(); ++ ++ java.util.UUID getUUID(); // CraftBukkit + } diff --git a/patches/net/minecraft/world/storage/MapData.java.patch b/patches/net/minecraft/world/storage/MapData.java.patch new file mode 100644 index 00000000..f7a449d7 --- /dev/null +++ b/patches/net/minecraft/world/storage/MapData.java.patch @@ -0,0 +1,180 @@ +--- ../src-base/minecraft/net/minecraft/world/storage/MapData.java ++++ ../src-work/minecraft/net/minecraft/world/storage/MapData.java +@@ -4,6 +4,7 @@ + import com.google.common.collect.Maps; + import java.util.List; + import java.util.Map; ++import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.entity.item.EntityItemFrame; + import net.minecraft.entity.player.EntityPlayer; +@@ -15,6 +16,9 @@ + import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.MathHelper; + import net.minecraft.world.World; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.map.CraftMapView; + + public class MapData extends WorldSavedData + { +@@ -25,13 +29,19 @@ + public boolean unlimitedTracking; + public byte scale; + public byte[] colors = new byte[16384]; +- public List playersArrayList = Lists.newArrayList(); +- private final Map playersHashMap = Maps.newHashMap(); ++ public List playersArrayList = Lists.newArrayList(); ++ private final Map playersHashMap = Maps.newHashMap(); + public Map mapDecorations = Maps.newLinkedHashMap(); + ++ public final CraftMapView mapView; ++ private CraftServer server; ++ private UUID uniqueId = null; ++ + public MapData(String mapname) + { + super(mapname); ++ mapView = new CraftMapView(this); ++ server = (CraftServer) org.bukkit.Bukkit.getServer(); + } + + public void calculateMapCenter(double x, double z, int mapScale) +@@ -45,7 +55,31 @@ + + public void readFromNBT(NBTTagCompound nbt) + { +- this.dimension = nbt.getInteger("dimension"); ++ // this.dimension = nbt.getInteger("dimension"); ++ // CraftBukkit start ++ int dimension = nbt.getInteger("dimension"); ++ ++ if (dimension >= 10) { ++ long least = nbt.getLong("UUIDLeast"); ++ long most = nbt.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ this.uniqueId = new UUID(most, least); ++ ++ CraftWorld world = (CraftWorld) server.getWorld(this.uniqueId); ++ // Check if the stored world details are correct. ++ if (world == null) { ++ /* All Maps which do not have their valid world loaded are set to a dimension which hopefully won't be reached. ++ This is to prevent them being corrupted with the wrong map data. */ ++ dimension = 127; ++ } else { ++ dimension = (byte) world.getHandle().dimension; ++ } ++ } ++ } ++ ++ this.dimension = dimension; ++ // CraftBukkit end + this.xCenter = nbt.getInteger("xCenter"); + this.zCenter = nbt.getInteger("zCenter"); + this.scale = nbt.getByte("scale"); +@@ -97,6 +131,25 @@ + + public NBTTagCompound writeToNBT(NBTTagCompound compound) + { ++ // CraftBukkit start ++ if (this.dimension >= 10) { ++ if (this.uniqueId == null) { ++ for (org.bukkit.World world : server.getWorlds()) { ++ CraftWorld cWorld = (CraftWorld) world; ++ if (cWorld.getHandle().dimension == this.dimension) { ++ this.uniqueId = cWorld.getUID(); ++ break; ++ } ++ } ++ } ++ /* Perform a second check to see if a matching world was found, this is a necessary ++ change incase Maps are forcefully unlinked from a World and lack a UID.*/ ++ if (this.uniqueId != null) { ++ compound.setLong("UUIDLeast", this.uniqueId.getLeastSignificantBits()); ++ compound.setLong("UUIDMost", this.uniqueId.getMostSignificantBits()); ++ } ++ } ++ // CraftBukkit end + compound.setInteger("dimension", this.dimension); + compound.setInteger("xCenter", this.xCenter); + compound.setInteger("zCenter", this.zCenter); +@@ -113,7 +166,7 @@ + { + if (!this.playersHashMap.containsKey(player)) + { +- MapData.MapInfo mapdata$mapinfo = new MapData.MapInfo(player); ++ MapInfo mapdata$mapinfo = new MapInfo(player); + this.playersHashMap.put(player, mapdata$mapinfo); + this.playersArrayList.add(mapdata$mapinfo); + } +@@ -125,7 +178,7 @@ + + for (int i = 0; i < this.playersArrayList.size(); ++i) + { +- MapData.MapInfo mapdata$mapinfo1 = this.playersArrayList.get(i); ++ MapInfo mapdata$mapinfo1 = this.playersArrayList.get(i); + + if (!mapdata$mapinfo1.player.isDead && (mapdata$mapinfo1.player.inventory.hasItemStack(mapStack) || mapStack.isOnItemFrame())) + { +@@ -268,7 +321,7 @@ + @Nullable + public Packet getMapPacket(ItemStack mapStack, World worldIn, EntityPlayer player) + { +- MapData.MapInfo mapdata$mapinfo = this.playersHashMap.get(player); ++ MapInfo mapdata$mapinfo = this.playersHashMap.get(player); + return mapdata$mapinfo == null ? null : mapdata$mapinfo.getPacket(mapStack); + } + +@@ -276,19 +329,19 @@ + { + super.markDirty(); + +- for (MapData.MapInfo mapdata$mapinfo : this.playersArrayList) ++ for (MapInfo mapdata$mapinfo : this.playersArrayList) + { + mapdata$mapinfo.update(x, y); + } + } + +- public MapData.MapInfo getMapInfo(EntityPlayer player) ++ public MapInfo getMapInfo(EntityPlayer player) + { +- MapData.MapInfo mapdata$mapinfo = this.playersHashMap.get(player); ++ MapInfo mapdata$mapinfo = this.playersHashMap.get(player); + + if (mapdata$mapinfo == null) + { +- mapdata$mapinfo = new MapData.MapInfo(player); ++ mapdata$mapinfo = new MapInfo(player); + this.playersHashMap.put(player, mapdata$mapinfo); + this.playersArrayList.add(mapdata$mapinfo); + } +@@ -315,14 +368,25 @@ + @Nullable + public Packet getPacket(ItemStack stack) + { ++ org.bukkit.craftbukkit.map.RenderData render = MapData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); ++ ++ java.util.Collection icons = new java.util.ArrayList<>(); ++ ++ for ( org.bukkit.map.MapCursor cursor : render.cursors) { ++ ++ if (cursor.isVisible()) { ++ icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection())); ++ } ++ } ++ + if (this.isDirty) + { + this.isDirty = false; +- return new SPacketMaps(stack.getMetadata(), MapData.this.scale, MapData.this.trackingPosition, MapData.this.mapDecorations.values(), MapData.this.colors, this.minX, this.minY, this.maxX + 1 - this.minX, this.maxY + 1 - this.minY); ++ return new SPacketMaps(stack.getMetadata(), MapData.this.scale, MapData.this.trackingPosition, icons, render.buffer, this.minX, this.minY, this.maxX + 1 - this.minX, this.maxY + 1 - this.minY); + } + else + { +- return this.tick++ % 5 == 0 ? new SPacketMaps(stack.getMetadata(), MapData.this.scale, MapData.this.trackingPosition, MapData.this.mapDecorations.values(), MapData.this.colors, 0, 0, 0, 0) : null; ++ return this.tick++ % 5 == 0 ? new SPacketMaps(stack.getMetadata(), MapData.this.scale, MapData.this.trackingPosition, icons, render.buffer, 0, 0, 0, 0) : null; + } + } + diff --git a/patches/net/minecraft/world/storage/SaveHandler.java.patch b/patches/net/minecraft/world/storage/SaveHandler.java.patch new file mode 100644 index 00000000..a2c9bb90 --- /dev/null +++ b/patches/net/minecraft/world/storage/SaveHandler.java.patch @@ -0,0 +1,116 @@ +--- ../src-base/minecraft/net/minecraft/world/storage/SaveHandler.java ++++ ../src-work/minecraft/net/minecraft/world/storage/SaveHandler.java +@@ -6,8 +6,11 @@ + import java.io.FileInputStream; + import java.io.FileOutputStream; + import java.io.IOException; ++import java.io.InputStream; ++import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.entity.player.EntityPlayer; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.nbt.CompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; +@@ -19,6 +22,7 @@ + import net.minecraft.world.gen.structure.template.TemplateManager; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.entity.CraftPlayer; + + public class SaveHandler implements ISaveHandler, IPlayerFileData + { +@@ -31,6 +35,8 @@ + private final TemplateManager structureTemplateManager; + protected final DataFixer dataFixer; + ++ private UUID uuid = null; ++ + public SaveHandler(File p_i46648_1_, String saveDirectoryNameIn, boolean p_i46648_3_, DataFixer dataFixerIn) + { + this.dataFixer = dataFixerIn; +@@ -221,6 +227,14 @@ + + if (nbttagcompound != null) + { ++ if (player instanceof EntityPlayerMP) { ++ CraftPlayer playerCB = (CraftPlayer) player.getBukkitEntity(); ++ // Only update first played if it is older than the one we have ++ long modified = new File(this.playersDirectory, player.getUniqueID().toString() + ".dat").lastModified(); ++ if (modified < playerCB.getFirstPlayed()) { ++ playerCB.setFirstPlayed(modified); ++ } ++ } + player.readFromNBT(this.dataFixer.process(FixTypes.PLAYER, nbttagcompound)); + } + +@@ -228,6 +242,20 @@ + return nbttagcompound; + } + ++ public NBTTagCompound getPlayerData(String s) { ++ try { ++ File file1 = new File(this.playersDirectory, s + ".dat"); ++ ++ if (file1.exists()) { ++ return CompressedStreamTools.readCompressed((InputStream) (new FileInputStream(file1))); ++ } ++ } catch (Exception exception) { ++ LOGGER.warn("Failed to load player data for " + s); ++ } ++ ++ return null; ++ } ++ + public IPlayerFileData getPlayerNBTManager() + { + return this; +@@ -284,4 +312,48 @@ + } + return null; + } ++ ++ public UUID getUUID() { ++ if (uuid != null) return uuid; ++ File file1 = new File(this.worldDirectory, "uid.dat"); ++ if (file1.exists()) { ++ DataInputStream dis = null; ++ try { ++ dis = new DataInputStream(new FileInputStream(file1)); ++ return uuid = new UUID(dis.readLong(), dis.readLong()); ++ } catch (IOException ex) { ++ LOGGER.warn("Failed to read " + file1 + ", generating new random UUID", ex); ++ } finally { ++ if (dis != null) { ++ try { ++ dis.close(); ++ } catch (IOException ex) { ++ // NOOP ++ } ++ } ++ } ++ } ++ uuid = UUID.randomUUID(); ++ DataOutputStream dos = null; ++ try { ++ dos = new DataOutputStream(new FileOutputStream(file1)); ++ dos.writeLong(uuid.getMostSignificantBits()); ++ dos.writeLong(uuid.getLeastSignificantBits()); ++ } catch (IOException ex) { ++ LOGGER.warn("Failed to write " + file1, ex); ++ } finally { ++ if (dos != null) { ++ try { ++ dos.close(); ++ } catch (IOException ex) { ++ // NOOP ++ } ++ } ++ } ++ return uuid; ++ } ++ ++ public File getPlayerDir() { ++ return playersDirectory; ++ } + } diff --git a/patches/net/minecraft/world/storage/SaveHandlerMP.java.patch b/patches/net/minecraft/world/storage/SaveHandlerMP.java.patch new file mode 100644 index 00000000..6ba48458 --- /dev/null +++ b/patches/net/minecraft/world/storage/SaveHandlerMP.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/world/storage/SaveHandlerMP.java ++++ ../src-work/minecraft/net/minecraft/world/storage/SaveHandlerMP.java +@@ -1,6 +1,8 @@ + package net.minecraft.world.storage; + + import java.io.File; ++import java.util.UUID; ++ + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.world.MinecraftException; + import net.minecraft.world.WorldProvider; +@@ -57,4 +59,9 @@ + { + return null; + } ++ ++ @Override ++ public UUID getUUID() { ++ return null; ++ } + } diff --git a/patches/net/minecraft/world/storage/WorldInfo.java.patch b/patches/net/minecraft/world/storage/WorldInfo.java.patch new file mode 100644 index 00000000..6c9f1f9d --- /dev/null +++ b/patches/net/minecraft/world/storage/WorldInfo.java.patch @@ -0,0 +1,134 @@ +--- ../src-base/minecraft/net/minecraft/world/storage/WorldInfo.java ++++ ../src-work/minecraft/net/minecraft/world/storage/WorldInfo.java +@@ -6,7 +6,9 @@ + import javax.annotation.Nullable; + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.crash.ICrashReportDetail; ++import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.play.server.SPacketServerDifficulty; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.datafix.DataFixer; + import net.minecraft.util.datafix.FixTypes; +@@ -17,10 +19,14 @@ + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.GameRules; + import net.minecraft.world.GameType; ++import net.minecraft.world.WorldServer; + import net.minecraft.world.WorldSettings; + import net.minecraft.world.WorldType; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; ++import org.bukkit.Bukkit; ++import org.bukkit.event.weather.ThunderChangeEvent; ++import org.bukkit.event.weather.WeatherChangeEvent; + + public class WorldInfo + { +@@ -65,8 +71,10 @@ + private int borderWarningTime = 15; + private final Map dimensionData = Maps.newHashMap(); + private GameRules gameRules = new GameRules(); +- private java.util.Map additionalProperties; ++ private Map additionalProperties; + ++ public WorldServer world; ++ + protected WorldInfo() + { + } +@@ -161,6 +169,7 @@ + this.thunderTime = nbt.getInteger("thunderTime"); + this.thundering = nbt.getBoolean("thundering"); + this.hardcore = nbt.getBoolean("hardcore"); ++ this.dimension = nbt.getInteger("dimension"); + + if (nbt.hasKey("initialized", 99)) + { +@@ -376,6 +385,7 @@ + + nbt.setBoolean("DifficultyLocked", this.difficultyLocked); + nbt.setTag("GameRules", this.gameRules.writeToNBT()); ++ nbt.setInteger("dimension", this.dimension); + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); + + for (Entry entry : this.dimensionData.entrySet()) +@@ -511,6 +521,14 @@ + + public void setThundering(boolean thunderingIn) + { ++ org.bukkit.World world = Bukkit.getWorld(getWorldName()); ++ if (world != null) { ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thunderingIn); ++ Bukkit.getServer().getPluginManager().callEvent(thunder); ++ if (thunder.isCancelled()) { ++ return; ++ } ++ } + this.thundering = thunderingIn; + } + +@@ -531,6 +549,14 @@ + + public void setRaining(boolean isRaining) + { ++ org.bukkit.World world = Bukkit.getWorld(getWorldName()); ++ if (world != null) { ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, isRaining); ++ Bukkit.getServer().getPluginManager().callEvent(weather); ++ if (weather.isCancelled()) { ++ return; ++ } ++ } + this.raining = isRaining; + } + +@@ -712,6 +738,10 @@ + public void setDifficulty(EnumDifficulty newDifficulty) + { + net.minecraftforge.common.ForgeHooks.onDifficultyChange(newDifficulty, this.difficulty); ++ SPacketServerDifficulty packet = new SPacketServerDifficulty(this.getDifficulty(), this.isDifficultyLocked()); ++ for (EntityPlayerMP player :(java.util.List) (java.util.List) world.playerEntities) { ++ player.connection.sendPacket(packet); ++ } + this.difficulty = newDifficulty; + } + +@@ -815,7 +845,7 @@ + * Used by Forge to store the dimensions available to a world + * @param additionalProperties + */ +- public void setAdditionalProperties(java.util.Map additionalProperties) ++ public void setAdditionalProperties(Map additionalProperties) + { + // one time set for this + if (this.additionalProperties == null) +@@ -869,4 +899,28 @@ + { + return this.versionName; + } ++ ++ // CraftBukkit start - Check if the name stored in NBT is the correct one ++ public void checkName(String name) { ++ if (!this.levelName.equals(name)) { ++ this.levelName = name; ++ } ++ } ++ // CraftBukkit end ++ ++ /** ++ * Sets the Dimension. ++ */ ++ public void setDimension(int dim) ++ { ++ this.dimension = dim; ++ } ++ ++ public int getDimension() { ++ return this.dimension; ++ } ++ ++ public void tick() { ++ totalTime++; ++ } + } diff --git a/patches/net/minecraftforge/common/DimensionManager.java.patch b/patches/net/minecraftforge/common/DimensionManager.java.patch new file mode 100644 index 00000000..7cb5e105 --- /dev/null +++ b/patches/net/minecraftforge/common/DimensionManager.java.patch @@ -0,0 +1,237 @@ +--- ../src-base/minecraft/net/minecraftforge/common/DimensionManager.java ++++ ../src-work/minecraft/net/minecraftforge/common/DimensionManager.java +@@ -47,18 +47,22 @@ + + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; +-import net.minecraft.world.DimensionType; +-import net.minecraft.world.MinecraftException; +-import net.minecraft.world.World; +-import net.minecraft.world.ServerWorldEventHandler; +-import net.minecraft.world.WorldProvider; +-import net.minecraft.world.WorldServer; +-import net.minecraft.world.WorldServerMulti; ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecraft.world.*; ++import net.minecraft.world.chunk.storage.AnvilSaveHandler; + import net.minecraft.world.storage.ISaveHandler; ++import net.minecraft.world.storage.WorldInfo; ++import net.minecraftforge.common.util.EnumHelper; + import net.minecraftforge.event.world.WorldEvent; ++import net.minecraftforge.event.world.WorldEvent.Load; ++import net.minecraftforge.fml.common.FMLCommonHandler; + import net.minecraftforge.fml.common.FMLLog; + + import javax.annotation.Nullable; ++import org.bukkit.World.Environment; ++import org.bukkit.WorldCreator; ++import org.bukkit.event.world.WorldLoadEvent; ++import org.bukkit.generator.ChunkGenerator; + + public class DimensionManager + { +@@ -83,6 +87,7 @@ + private static final IntSet usedIds = new IntOpenHashSet(); + private static final ConcurrentMap weakWorldMap = new MapMaker().weakKeys().weakValues().makeMap(); + private static final Multiset leakedWorlds = HashMultiset.create(); ++ private static ArrayList bukkitDimensions = new ArrayList(); // used to keep track of Bukkit dimensions + + /** + * Returns a list of dimensions associated with this DimensionType. +@@ -138,6 +143,12 @@ + { + usedIds.add(id); + } ++ ++ // Kettle - register Environment to Bukkit ++ if (id != -1 && id != 0 && id != 1) // ignore vanilla ++ { ++ registerBukkitDimension(id, type.getName()); ++ } + } + + /** +@@ -209,11 +220,16 @@ + { + worlds.put(id, world); + weakWorldMap.put(world, world); ++ // handle all worlds for bukkit ++ if(!FMLCommonHandler.instance().getMinecraftServerInstance().worldServerList.contains(world)){ ++ FMLCommonHandler.instance().getMinecraftServerInstance().worldServerList.add(world); ++ } + server.worldTickTimes.put(id, new long[100]); + FMLLog.log.info("Loading dimension {} ({}) ({})", id, world.getWorldInfo().getWorldName(), world.getMinecraftServer()); + } + else + { ++ FMLCommonHandler.instance().getMinecraftServerInstance().worldServerList.remove(getWorld(id)); // Kettle - remove world from our new world arraylist + worlds.remove(id); + server.worldTickTimes.remove(id); + FMLLog.log.info("Unloading dimension {}", id); +@@ -257,12 +273,40 @@ + return; // If a provider hasn't been registered then we can't hotload the dim + } + MinecraftServer mcServer = overworld.getMinecraftServer(); +- ISaveHandler savehandler = overworld.getSaveHandler(); +- //WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo()); ++ WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo()); + +- WorldServer world = (dim == 0 ? overworld : (WorldServer)(new WorldServerMulti(mcServer, savehandler, dim, overworld, mcServer.profiler).init())); ++ String worldType; ++ String name; ++ Environment environment = Environment.getEnvironment(dim); ++ if(dim >= -1 && dim <= 1){ ++ if((dim == -1 && !mcServer.getAllowNether()) || (dim == 1 && mcServer.server.getAllowEnd())){ ++ return; ++ } ++ worldType = environment.toString().toLowerCase(); ++ name = "DIM" + dim; ++ }else{ ++ WorldProvider provider = WorldProvider.getProviderForDimension(dim); ++ worldType = provider.getClass().getSimpleName().toLowerCase(); ++ worldType = worldType.replace("worldprovider", ""); ++ worldType = worldType.replace("provider", ""); ++ if(org.bukkit.World.Environment.getEnvironment(DimensionManager.getProviderType(dim).getId()) == null){ ++ environment = DimensionManager.registerBukkitDimension(DimensionManager.getProviderType(dim).getId(), worldType); ++ } ++ ++ name = provider.getSaveFolder(); ++ } ++ ++ ChunkGenerator generator = mcServer.server.getGenerator(name); ++ if (mcServer instanceof DedicatedServer) { ++ worldSettings.setGeneratorOptions(((DedicatedServer) mcServer).getStringProperty("generator-settings", "")); ++ } ++ WorldInfo worldInfo = new WorldInfo(worldSettings, name); ++ WorldServer world = (dim == 0 ? overworld : (WorldServer)(new WorldServerMulti(mcServer, new AnvilSaveHandler(mcServer.server.getWorldContainer(), name, true, mcServer.getDataFixer()), dim, overworld, mcServer.profiler, worldInfo, environment, generator).init())); ++ ++ mcServer.getPlayerList().setPlayerManager(mcServer.worldServerList.toArray(new WorldServer[mcServer.worldServerList.size()])); + world.addEventListener(new ServerWorldEventHandler(mcServer, world)); + MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world)); ++ mcServer.server.getPluginManager().callEvent(new WorldLoadEvent(world.getWorld())); + if (!mcServer.isSinglePlayer()) + { + world.getWorldInfo().setGameType(mcServer.getGameType()); +@@ -390,20 +434,7 @@ + FMLLog.log.debug("Aborting unload for dimension {} as status changed", id); + continue; + } +- try +- { +- w.saveAllChunks(true, null); +- } +- catch (MinecraftException e) +- { +- FMLLog.log.error("Caught an exception while saving all chunks:", e); +- } +- finally +- { +- MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w)); +- w.flush(); +- setWorld(id, null, w.getMinecraftServer()); +- } ++ FMLCommonHandler.instance().getMinecraftServerInstance().server.unloadWorld(w.getWorld(), true); // Kettle + } + } + +@@ -499,4 +530,100 @@ + return null; + } + } ++ ++ public static WorldServer initDimension(WorldCreator creator, WorldSettings worldSettings){ ++ WorldServer overworld = getWorld(0); ++ if(overworld == null){ ++ throw new RuntimeException("Cannot Hotload Dim: Overworld is not Loaded!"); ++ } ++ ++ MinecraftServer mcServer = overworld.getMinecraftServer(); ++ String name; ++ ++ DimensionType type = DimensionType.OVERWORLD; ++ try{ ++ if(creator.environment() != null){ ++ type = DimensionType.getById(creator.environment().getId()); ++ } ++ }catch (IllegalArgumentException e){ ++ ++ } ++ ++ Environment environment = creator.environment(); ++ name = creator.name(); ++ int dim = 0; ++ ++ AnvilSaveHandler saveHandler = new AnvilSaveHandler(mcServer.server.getWorldContainer(), name, true, mcServer.dataFixer); ++ ++ if(saveHandler.loadWorldInfo() != null){ ++ int savedDim = saveHandler.loadWorldInfo().getDimension(); ++ if(savedDim != 0 && savedDim != -1 && savedDim != 1){ ++ dim = savedDim; ++ } ++ } ++ if(dim == 0){ ++ dim = getNextFreeDimId(); ++ } ++ ++ if (!isDimensionRegistered(dim)) // handle reloads properly ++ { ++ registerDimension(dim, type); ++ addBukkitDimension(dim); ++ } ++ ++ ChunkGenerator generator = creator.generator(); ++ if(mcServer instanceof DedicatedServer){ ++ worldSettings.setGeneratorOptions(((DedicatedServer) mcServer).getStringProperty("generator-settings", "")); ++ } ++ ++ WorldInfo worldInfo = saveHandler.loadWorldInfo(); ++ if(worldInfo == null){ ++ worldInfo = new WorldInfo(worldSettings, name); ++ } ++ ++ WorldServer world = (WorldServer) new WorldServerMulti(mcServer, saveHandler, dim, overworld,mcServer.profiler, worldInfo, environment, generator).init(); ++ world.initialize(worldSettings); ++ world.provider.setDimension(dim); ++ ++ mcServer.getPlayerList().setPlayerManager(mcServer.worldServerList.toArray(new WorldServer[mcServer.worldServerList.size()])); ++ ++ world.addEventListener(new ServerWorldEventHandler(mcServer, world)); ++ MinecraftForge.EVENT_BUS.post(new Load(world)); ++ if(!mcServer.isSinglePlayer()){ ++ world.getWorldInfo().setGameType(mcServer.getGameType()); ++ } ++ return world; ++ } ++ ++ public static org.bukkit.World.Environment registerBukkitDimension(int dim, String worldType) { ++ Environment env = Environment.getEnvironment(dim); ++ if(env == null){ ++ worldType = worldType.replace("WorldProvider",""); ++ env = EnumHelper.addBukkitEnvironment(dim,worldType.toUpperCase()); ++ Environment.registerEnvironment(env); ++ } ++ return env; ++ } ++ ++ public static void addBukkitDimension(int dim) ++ { ++ if (!bukkitDimensions.contains(dim)) ++ bukkitDimensions.add(dim); ++ } ++ ++ public static void removeBukkitDimension(int dim) ++ { ++ if (bukkitDimensions.contains(dim)) ++ bukkitDimensions.remove(bukkitDimensions.indexOf(dim)); ++ } ++ ++ public static ArrayList getBukkitDimensionIDs() ++ { ++ return bukkitDimensions; ++ } ++ ++ public static boolean isBukkitDimension(int dim) ++ { ++ return bukkitDimensions.contains(dim); ++ } + } diff --git a/patches/net/minecraftforge/common/ForgeHooks.java.patch b/patches/net/minecraftforge/common/ForgeHooks.java.patch new file mode 100644 index 00000000..05d69df3 --- /dev/null +++ b/patches/net/minecraftforge/common/ForgeHooks.java.patch @@ -0,0 +1,29 @@ +--- ../src-base/minecraft/net/minecraftforge/common/ForgeHooks.java ++++ ../src-work/minecraft/net/minecraftforge/common/ForgeHooks.java +@@ -450,7 +450,7 @@ + } + else + { +- ResourceLocation resourcelocation = EntityList.func_191301_a(this.objectMouseOver.entityHit); ++ ResourceLocation resourcelocation = EntityList.getKey(this.objectMouseOver.entityHit); + + if (resourcelocation == null || !EntityList.ENTITY_EGGS.containsKey(resourcelocation)) + { +@@ -472,7 +472,7 @@ + } + else if (this.objectMouseOver.typeOfHit == RayTraceResult.Type.ENTITY) + { +- s = EntityList.func_191301_a(this.objectMouseOver.entityHit).toString(); ++ s = EntityList.getKey(this.objectMouseOver.entityHit).toString(); + } + + LOGGER.warn("Picking on: [{}] {} gave null item", new Object[] {this.objectMouseOver.typeOfHit, s}); +@@ -993,7 +993,7 @@ + } + + /** +- * Default implementation of IRecipe.func_179532_b {getRemainingItems} because ++ * Default implementation of IRecipe.getRemainingItems {getRemainingItems} because + * this is just copy pasted over a lot of recipes. + * + * @param inv Crafting inventory diff --git a/patches/net/minecraftforge/common/ForgeVersion.java.patch b/patches/net/minecraftforge/common/ForgeVersion.java.patch new file mode 100644 index 00000000..dac4f3b9 --- /dev/null +++ b/patches/net/minecraftforge/common/ForgeVersion.java.patch @@ -0,0 +1,16 @@ +--- ../src-base/minecraft/net/minecraftforge/common/ForgeVersion.java ++++ ../src-work/minecraft/net/minecraftforge/common/ForgeVersion.java +@@ -59,7 +59,7 @@ + //This number is incremented every time a interface changes or new major feature is added, and reset every Minecraft version + public static final int revisionVersion = 5; + //This number is incremented every time Jenkins builds Forge, and never reset. Should always be 0 in the repo code. +- public static final int buildVersion = 0; ++ public static final int buildVersion = 2838; + // This is the minecraft version we're building for - used in various places in Forge/FML code + public static final String mcVersion = "1.12.2"; + // This is the MCP data version we're using +@@ -367,4 +367,3 @@ + return ret == null ? PENDING_CHECK : ret; + } + } +- diff --git a/patches/net/minecraftforge/common/WorldSpecificSaveHandler.java.patch b/patches/net/minecraftforge/common/WorldSpecificSaveHandler.java.patch new file mode 100644 index 00000000..3738df88 --- /dev/null +++ b/patches/net/minecraftforge/common/WorldSpecificSaveHandler.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraftforge/common/WorldSpecificSaveHandler.java ++++ ../src-work/minecraft/net/minecraftforge/common/WorldSpecificSaveHandler.java +@@ -21,6 +21,7 @@ + + import java.io.File; + import java.io.IOException; ++import java.util.UUID; + + import net.minecraft.world.gen.structure.template.TemplateManager; + import org.apache.logging.log4j.Level; +@@ -106,4 +107,8 @@ + return parent.getStructureTemplateManager(); + } + ++ @Override ++ public UUID getUUID() { ++ return parent.getUUID(); ++ } + } diff --git a/patches/net/minecraftforge/common/network/ForgeNetworkHandler.java.patch b/patches/net/minecraftforge/common/network/ForgeNetworkHandler.java.patch new file mode 100644 index 00000000..2998cb7c --- /dev/null +++ b/patches/net/minecraftforge/common/network/ForgeNetworkHandler.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraftforge/common/network/ForgeNetworkHandler.java ++++ ../src-work/minecraft/net/minecraftforge/common/network/ForgeNetworkHandler.java +@@ -54,4 +54,8 @@ + clientChannel.pipeline().addAfter(handlerName, "DimensionHandler", new DimensionMessageHandler()); + clientChannel.pipeline().addAfter(handlerName, "FluidIdRegistryHandler", new FluidIdRegistryMessageHandler()); + } ++ ++ public static FMLEmbeddedChannel getServerChannel(){ ++ return channelPair.get(Side.SERVER); ++ } + } diff --git a/patches/net/minecraftforge/common/util/Constants.java.patch b/patches/net/minecraftforge/common/util/Constants.java.patch new file mode 100644 index 00000000..4e5dc1cc --- /dev/null +++ b/patches/net/minecraftforge/common/util/Constants.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraftforge/common/util/Constants.java ++++ ../src-work/minecraft/net/minecraftforge/common/util/Constants.java +@@ -31,7 +31,7 @@ + * NBT Tag type IDS, used when storing the nbt to disc, Should align with NBTBase.getId, + * table used in NBTBase.func_150283_g + * +- * Main use is checking tag type in NBTTagCompound.func_150297_b(String, int) ++ * Main use is checking tag type in NBTTagCompound.hasKey(String, int) + * + */ + public static class NBT diff --git a/patches/net/minecraftforge/common/util/EnumHelper.java.patch b/patches/net/minecraftforge/common/util/EnumHelper.java.patch new file mode 100644 index 00000000..0463559c --- /dev/null +++ b/patches/net/minecraftforge/common/util/EnumHelper.java.patch @@ -0,0 +1,51 @@ +--- ../src-base/minecraft/net/minecraftforge/common/util/EnumHelper.java ++++ ../src-work/minecraft/net/minecraftforge/common/util/EnumHelper.java +@@ -19,15 +19,15 @@ + + package net.minecraftforge.common.util; + +-import java.lang.reflect.*; +-import java.util.*; +-import java.util.function.BiPredicate; +- + import com.google.common.base.Predicate; + import com.google.common.collect.Lists; +- +-import net.minecraftforge.fml.common.EnhancedRuntimeException; +-import net.minecraftforge.fml.common.FMLLog; ++import java.lang.reflect.Constructor; ++import java.lang.reflect.Field; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import java.util.List; ++import java.util.function.BiPredicate; ++import javax.annotation.Nullable; + import net.minecraft.block.BlockPressurePlate.Sensitivity; + import net.minecraft.block.material.Material; + import net.minecraft.enchantment.EnumEnchantmentType; +@@ -51,10 +51,11 @@ + import net.minecraft.world.IBlockAccess; + import net.minecraft.world.gen.structure.StructureStrongholdPieces.Stronghold.Door; + import net.minecraftforge.classloading.FMLForgePlugin; ++import net.minecraftforge.fml.common.EnhancedRuntimeException; ++import net.minecraftforge.fml.common.FMLLog; + import org.apache.commons.lang3.ArrayUtils; ++import org.bukkit.World.Environment; + +-import javax.annotation.Nullable; +- + public class EnumHelper + { + private static Object reflectionFactory = null; +@@ -428,4 +429,11 @@ + setup(); + } + } ++ ++ public static Environment addBukkitEnvironment(int id, String name){ ++ if(!isSetup){ ++ setup(); ++ } ++ return (Environment) addEnum(Environment.class, name, new Class[]{Integer.TYPE}, new Object[]{Integer.valueOf(id)}); ++ } + } diff --git a/patches/net/minecraftforge/event/ForgeEventFactory.java.patch b/patches/net/minecraftforge/event/ForgeEventFactory.java.patch new file mode 100644 index 00000000..b1764247 --- /dev/null +++ b/patches/net/minecraftforge/event/ForgeEventFactory.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraftforge/event/ForgeEventFactory.java ++++ ../src-work/minecraft/net/minecraftforge/event/ForgeEventFactory.java +@@ -585,7 +585,7 @@ + { + Entity e = itr.next(); + double dist = e.getDistance(p.xCoord, p.yCoord, p.zCoord) / diameter; +- if (e.func_180427_aV() || dist > 1.0F) itr.remove(); ++ if (e.isImmuneToExplosions() || dist > 1.0F) itr.remove(); + } + */ + MinecraftForge.EVENT_BUS.post(new ExplosionEvent.Detonate(world, explosion, list)); diff --git a/patches/net/minecraftforge/fml/client/FMLFolderResourcePack.java.patch b/patches/net/minecraftforge/fml/client/FMLFolderResourcePack.java.patch new file mode 100644 index 00000000..7d1db807 --- /dev/null +++ b/patches/net/minecraftforge/fml/client/FMLFolderResourcePack.java.patch @@ -0,0 +1,14 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/client/FMLFolderResourcePack.java ++++ ../src-work/minecraft/net/minecraftforge/fml/client/FMLFolderResourcePack.java +@@ -45,9 +45,9 @@ + } + + @Override +- protected boolean hasResourceName(String p_110593_1_) ++ protected boolean hasResourceName(String name) + { +- return super.hasResourceName(p_110593_1_); ++ return super.hasResourceName(name); + } + @Override + public String getPackName() diff --git a/patches/net/minecraftforge/fml/client/config/GuiSelectStringEntries.java.patch b/patches/net/minecraftforge/fml/client/config/GuiSelectStringEntries.java.patch new file mode 100644 index 00000000..1c1f14e4 --- /dev/null +++ b/patches/net/minecraftforge/fml/client/config/GuiSelectStringEntries.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/client/config/GuiSelectStringEntries.java ++++ ../src-work/minecraft/net/minecraftforge/fml/client/config/GuiSelectStringEntries.java +@@ -195,7 +195,7 @@ + } + + @Override +- public void updatePosition(int p_192633_1_, int p_192633_2_, int p_192633_3_, float p_192633_4_){} ++ public void updatePosition(int slotIndex, int x, int y, float partialTicks){} + } + + public static interface IGuiSelectStringListEntry extends GuiListExtended.IGuiListEntry diff --git a/patches/net/minecraftforge/fml/common/FMLCommonHandler.java.patch b/patches/net/minecraftforge/fml/common/FMLCommonHandler.java.patch new file mode 100644 index 00000000..6bae1a45 --- /dev/null +++ b/patches/net/minecraftforge/fml/common/FMLCommonHandler.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/common/FMLCommonHandler.java ++++ ../src-work/minecraft/net/minecraftforge/fml/common/FMLCommonHandler.java +@@ -38,6 +38,7 @@ + import java.util.concurrent.FutureTask; + import java.util.concurrent.TimeUnit; + ++import com.mojang.authlib.properties.Property; + import net.minecraft.crash.CrashReport; + import net.minecraft.crash.CrashReportCategory; + import net.minecraft.entity.item.EntityItem; +@@ -507,7 +508,7 @@ + MinecraftServer server = getMinecraftServerInstance(); + Loader.instance().serverStopped(); + // FORCE the internal server to stop: hello optifine workaround! +- if (server!=null) ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, false, "field_71316"+"_v"); ++ if (server!=null) ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, false, "field_71316"+"_v", "u", "serverStopped"); + + // allow any pending exit to continue, clear exitLatch + CountDownLatch latch = exitLatch; diff --git a/patches/net/minecraftforge/fml/common/network/internal/FMLNetworkHandler.java.patch b/patches/net/minecraftforge/fml/common/network/internal/FMLNetworkHandler.java.patch new file mode 100644 index 00000000..8f717516 --- /dev/null +++ b/patches/net/minecraftforge/fml/common/network/internal/FMLNetworkHandler.java.patch @@ -0,0 +1,57 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/common/network/internal/FMLNetworkHandler.java ++++ ../src-work/minecraft/net/minecraftforge/fml/common/network/internal/FMLNetworkHandler.java +@@ -33,9 +33,12 @@ + import net.minecraft.entity.player.EntityPlayer; + import net.minecraft.entity.player.EntityPlayerMP; + import net.minecraft.inventory.Container; ++import net.minecraft.inventory.IInventory; + import net.minecraft.network.NetworkManager; + import net.minecraft.network.Packet; + import net.minecraft.server.management.PlayerList; ++import net.minecraft.tileentity.TileEntity; ++import net.minecraft.util.math.BlockPos; + import net.minecraft.world.World; + import net.minecraftforge.common.util.FakePlayer; + import net.minecraftforge.fml.common.FMLCommonHandler; +@@ -58,6 +61,11 @@ + import com.google.gson.JsonArray; + import com.google.gson.JsonObject; + import org.apache.commons.lang3.tuple.Pair; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.event.inventory.InventoryType; + + import javax.annotation.Nullable; + +@@ -88,6 +96,20 @@ + Container remoteGuiContainer = NetworkRegistry.INSTANCE.getRemoteGuiContainer(mc, entityPlayerMP, modGuiId, world, x, y, z); + if (remoteGuiContainer != null) + { ++ // Kettle start ++ if(remoteGuiContainer.getBukkitView() == null){ ++ TileEntity te = entityPlayer.world.getTileEntity(new BlockPos(x,y,z)); ++ if(te instanceof IInventory){ ++ remoteGuiContainer.setBukkitView(new CraftInventoryView(entityPlayer.getBukkitEntity(), new CraftInventory((IInventory) te), remoteGuiContainer)); ++ }else{ ++ remoteGuiContainer.setBukkitView(new CraftInventoryView(entityPlayer.getBukkitEntity(), Bukkit.createInventory(entityPlayer.getBukkitEntity(), InventoryType.CHEST), remoteGuiContainer)); ++ } ++ remoteGuiContainer = CraftEventFactory.callInventoryOpenEvent((EntityPlayerMP) entityPlayer, remoteGuiContainer, false); ++ if(remoteGuiContainer == null){ ++ return; ++ } ++ } ++ // Kettle end + entityPlayerMP.getNextWindowId(); + entityPlayerMP.closeContainer(); + int windowId = entityPlayerMP.currentWindowId; +@@ -210,7 +232,7 @@ + public static void enhanceStatusQuery(JsonObject jsonobject) + { + JsonObject fmlData = new JsonObject(); +- fmlData.addProperty("type", "FML"); ++ fmlData.addProperty("type", "BUKKIT"); // Show the Bukkit icon in server list. + JsonArray modList = new JsonArray(); + for (ModContainer mc : Loader.instance().getActiveModList()) + { diff --git a/patches/net/minecraftforge/fml/common/registry/EntityRegistry.java.patch b/patches/net/minecraftforge/fml/common/registry/EntityRegistry.java.patch new file mode 100644 index 00000000..ba3bf146 --- /dev/null +++ b/patches/net/minecraftforge/fml/common/registry/EntityRegistry.java.patch @@ -0,0 +1,62 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/common/registry/EntityRegistry.java ++++ ../src-work/minecraft/net/minecraftforge/fml/common/registry/EntityRegistry.java +@@ -19,6 +19,9 @@ + + package net.minecraftforge.fml.common.registry; + ++import com.google.common.collect.Maps; ++import net.minecraftforge.fml.common.Loader; ++ + import net.minecraft.entity.Entity; + import net.minecraft.entity.EntityList.EntityEggInfo; + import net.minecraft.entity.EntityLiving; +@@ -148,6 +151,9 @@ + private final BiMap, EntityRegistration> entityClassRegistrations = HashBiMap.create(); + private final Map, EntityEntry> entityClassEntries = GameData.getEntityClassMap(); + ++ public static Map, String> entityTypeMap = Maps.newHashMap(); // used by CraftCustomEntity ++ public static Map> entityClassMap = Maps.newHashMap(); // user by CraftWorld ++ + public static EntityRegistry instance() + { + return INSTANCE; +@@ -171,6 +177,7 @@ + public static void registerModEntity(ResourceLocation registryName, Class entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates) + { + instance().doModEntityRegistration(registryName, entityClass, entityName, id, mod, trackingRange, updateFrequency, sendsVelocityUpdates); ++ registerBukkitType(entityClass, entityName); + } + + /** +@@ -191,6 +198,7 @@ + { + instance().doModEntityRegistration(registryName, entityClass, entityName, id, mod, trackingRange, updateFrequency, sendsVelocityUpdates); + EntityRegistry.registerEgg(registryName, eggPrimary, eggSecondary); ++ registerBukkitType(entityClass, entityName); + } + + private void doModEntityRegistration(ResourceLocation registryName, Class entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates) +@@ -385,4 +393,23 @@ + this.entityClassRegistrations.put(entity, registration); + this.entityRegistrations.put(registration.container, registration); + } ++ ++ private static void registerBukkitType(Class entityClass, String entityName) { ++ ModContainer activeModContainer = Loader.instance().activeModContainer(); ++ String modId = "unknown"; ++ // fixup bad entity names from mods ++ if (entityName.contains(".")) { ++ if ((entityName.indexOf(".") + 1) < entityName.length()) ++ entityName = entityName.substring(entityName.indexOf(".") + 1, entityName.length()); ++ } ++ entityName = entityName.replace("entity", ""); ++ if (entityName.startsWith("ent")) ++ entityName = entityName.replace("ent", ""); ++ entityName = entityName.replaceAll("[^A-Za-z0-9]", ""); // remove all non-digits/alphanumeric ++ if (activeModContainer != null) ++ modId = activeModContainer.getModId(); ++ entityName = modId + "-" + entityName; ++ entityTypeMap.put(entityClass, entityName); ++ entityClassMap.put(entityName, entityClass); ++ } + } diff --git a/patches/net/minecraftforge/fml/relauncher/CoreModManager.java.patch b/patches/net/minecraftforge/fml/relauncher/CoreModManager.java.patch new file mode 100644 index 00000000..f7f8aa93 --- /dev/null +++ b/patches/net/minecraftforge/fml/relauncher/CoreModManager.java.patch @@ -0,0 +1,44 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/relauncher/CoreModManager.java ++++ ../src-work/minecraft/net/minecraftforge/fml/relauncher/CoreModManager.java +@@ -76,14 +76,13 @@ + private static List ignoredModFiles = Lists.newArrayList(); + private static Map> transformers = Maps.newHashMap(); + private static List loadPlugins; ++ public static boolean deobfuscatedEnvironment; + private static FMLTweaker tweaker; + private static File mcDir; + private static List candidateModFiles = Lists.newArrayList(); + private static List accessTransformers = Lists.newArrayList(); + private static Set rootNames = Sets.newHashSet(); + +- static boolean deobfuscatedEnvironment; +- + static + { + for(String cls : rootPlugins) +@@ -486,11 +485,10 @@ + try + { + coreModDir = coreModDir.getCanonicalFile(); ++ } catch (IOException e) { ++ throw new RuntimeException(String.format("Unable to canonicalize the coremod dir at %s", mcDir.getName()), ++ e); + } +- catch (IOException e) +- { +- throw new RuntimeException(String.format("Unable to canonicalize the coremod dir at %s", mcDir.getName()), e); +- } + if (!coreModDir.exists()) + { + coreModDir.mkdir(); +@@ -542,6 +540,10 @@ + FMLLog.log.error("The coremod {} is requesting minecraft version {} and minecraft is {}. It will be ignored.", coreModClass, + requiredMCVersion.value(), FMLInjectionData.mccversion); + return null; ++ } else if (requiredMCVersion != null) { ++ FMLLog.log.debug( ++ "The coremod {} requested minecraft version {} and minecraft is {}. It will be loaded.", ++ coreModClass, requiredMCVersion.value(), FMLInjectionData.mccversion); + } + else if (requiredMCVersion != null) + { diff --git a/patches/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java.patch b/patches/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java.patch new file mode 100644 index 00000000..2b2c54c2 --- /dev/null +++ b/patches/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java.patch @@ -0,0 +1,11 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java ++++ ../src-work/minecraft/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java +@@ -92,7 +92,7 @@ + FMLInjectionData.build(minecraftHome, classLoader); + redirectStdOutputToLog(); + FMLLog.log.info("Forge Mod Loader version {}.{}.{}.{} for Minecraft {} loading", FMLInjectionData.major, FMLInjectionData.minor, +- FMLInjectionData.rev, FMLInjectionData.build, FMLInjectionData.mccversion); ++ FMLInjectionData.rev, FMLInjectionData.build, FMLInjectionData.mccversion); + FMLLog.log.info("Java is {}, version {}, running on {}:{}:{}, installed at {}", System.getProperty("java.vm.name"), System.getProperty("java.version"), System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version"), System.getProperty("java.home")); + FMLLog.log.debug("Java classpath at launch is:"); + for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) diff --git a/patches/net/minecraftforge/fml/relauncher/ReflectionHelper.java.patch b/patches/net/minecraftforge/fml/relauncher/ReflectionHelper.java.patch new file mode 100644 index 00000000..4ba8c382 --- /dev/null +++ b/patches/net/minecraftforge/fml/relauncher/ReflectionHelper.java.patch @@ -0,0 +1,20 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/relauncher/ReflectionHelper.java ++++ ../src-work/minecraft/net/minecraftforge/fml/relauncher/ReflectionHelper.java +@@ -142,7 +142,7 @@ + * + * @param clazz The class to find the field on. + * @param fieldName The name of the field to find (used in developer environments, i.e. "maxStackSize"). +- * @param fieldObfName The obfuscated name of the field to find (used in obfuscated environments, i.e. "field_77777_bU"). ++ * @param fieldObfName The obfuscated name of the field to find (used in obfuscated environments, i.e. "maxStackSize"). + * If the name you are looking for is on a class that is never obfuscated, this should be null. + * + * @return The field with the specified name in the given class. +@@ -279,7 +279,7 @@ + * + * @param clazz The class to find the method on. + * @param methodName The name of the method to find (used in developer environments, i.e. "getWorldTime"). +- * @param methodObfName The obfuscated name of the method to find (used in obfuscated environments, i.e. "func_72820_D"). ++ * @param methodObfName The obfuscated name of the method to find (used in obfuscated environments, i.e. "getWorldTime"). + * If the name you are looking for is on a class that is never obfuscated, this should be null. + * @param parameterTypes The parameter types of the method to find. + * @return The method with the specified name and parameters in the given class. diff --git a/patches/net/minecraftforge/fml/relauncher/ServerLaunchWrapper.java.patch b/patches/net/minecraftforge/fml/relauncher/ServerLaunchWrapper.java.patch new file mode 100644 index 00000000..064049cd --- /dev/null +++ b/patches/net/minecraftforge/fml/relauncher/ServerLaunchWrapper.java.patch @@ -0,0 +1,40 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/relauncher/ServerLaunchWrapper.java ++++ ../src-work/minecraft/net/minecraftforge/fml/relauncher/ServerLaunchWrapper.java +@@ -20,18 +20,21 @@ + package net.minecraftforge.fml.relauncher; + + import java.lang.reflect.Method; ++import kettlefoundation.kettle.downloads.DownloadServerFiles; + +-import org.apache.logging.log4j.LogManager; +-import org.apache.logging.log4j.core.LoggerContext; +- + public class ServerLaunchWrapper { + ++ public static long beginTime; ++ + /** + * @param args + */ + public static void main(String[] args) + { +- new ServerLaunchWrapper().run(args); ++ System.out.println("Starting Kettle"); ++ DownloadServerFiles.downloadMinecraftServer(); ++ DownloadServerFiles.downloadServerLibraries(); ++ new ServerLaunchWrapper().run(args); + } + + private ServerLaunchWrapper() +@@ -56,9 +59,9 @@ + { + System.err.printf("We appear to be missing one or more essential library files.\n" + + "You will need to add them to your server before FML and Forge will run successfully."); +- e.printStackTrace(System.err); +- System.exit(1); ++ + } ++ beginTime = System.nanoTime(); + + try + { diff --git a/patches/net/minecraftforge/fml/relauncher/SideOnly.java.patch b/patches/net/minecraftforge/fml/relauncher/SideOnly.java.patch new file mode 100644 index 00000000..dabc2c3c --- /dev/null +++ b/patches/net/minecraftforge/fml/relauncher/SideOnly.java.patch @@ -0,0 +1,28 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/relauncher/SideOnly.java ++++ ../src-work/minecraft/net/minecraftforge/fml/relauncher/SideOnly.java +@@ -17,6 +17,25 @@ + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + ++/* ++ * Minecraft Forge ++ * Copyright (c) 2016-2018. ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation version 2.1 ++ * of the License. ++ * ++ * This library 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 ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ + package net.minecraftforge.fml.relauncher; + + import java.lang.annotation.ElementType; diff --git a/patches/net/minecraftforge/fml/server/FMLServerHandler.java.patch b/patches/net/minecraftforge/fml/server/FMLServerHandler.java.patch new file mode 100644 index 00000000..b3ba79df --- /dev/null +++ b/patches/net/minecraftforge/fml/server/FMLServerHandler.java.patch @@ -0,0 +1,15 @@ +--- ../src-base/minecraft/net/minecraftforge/fml/server/FMLServerHandler.java ++++ ../src-work/minecraft/net/minecraftforge/fml/server/FMLServerHandler.java +@@ -19,7 +19,11 @@ + + package net.minecraftforge.fml.server; + +-import java.io.*; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.FileNotFoundException; ++import java.io.IOException; ++import java.io.InputStream; + import java.util.Iterator; + import java.util.List; + import java.util.Set; diff --git a/patches/net/minecraftforge/oredict/ShapedOreRecipe.java.patch b/patches/net/minecraftforge/oredict/ShapedOreRecipe.java.patch new file mode 100644 index 00000000..6dcf6163 --- /dev/null +++ b/patches/net/minecraftforge/oredict/ShapedOreRecipe.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraftforge/oredict/ShapedOreRecipe.java ++++ ../src-work/minecraft/net/minecraftforge/oredict/ShapedOreRecipe.java +@@ -29,7 +29,6 @@ + import net.minecraft.util.NonNullList; + import net.minecraft.util.ResourceLocation; + import net.minecraft.world.World; +-import net.minecraftforge.common.ForgeHooks; + import net.minecraftforge.common.crafting.CraftingHelper; + import net.minecraftforge.common.crafting.CraftingHelper.ShapedPrimer; + import net.minecraftforge.common.crafting.IShapedRecipe; +@@ -41,7 +40,6 @@ + import java.util.Map.Entry; + + import javax.annotation.Nonnull; +-import javax.annotation.Nullable; + + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -49,6 +47,7 @@ + import com.google.gson.JsonElement; + import com.google.gson.JsonObject; + import com.google.gson.JsonSyntaxException; ++import org.bukkit.inventory.Recipe; + + public class ShapedOreRecipe extends IForgeRegistryEntry.Impl implements IShapedRecipe + { +@@ -188,9 +187,9 @@ + } + + @Override +- public boolean canFit(int p_194133_1_, int p_194133_2_) ++ public boolean canFit(int width, int height) + { +- return p_194133_1_ >= this.width && p_194133_2_ >= this.height; ++ return width >= this.width && height >= this.height; + } + + public static ShapedOreRecipe factory(JsonContext context, JsonObject json) +@@ -254,4 +253,14 @@ + ItemStack result = CraftingHelper.getItemStack(JsonUtils.getJsonObject(json, "result"), context); + return new ShapedOreRecipe(group.isEmpty() ? null : new ResourceLocation(group), result, primer); + } ++ ++ @Override ++ public Recipe toBukkitRecipe() { ++ throw new UnsupportedOperationException("Cannot get bukkit-recipe from ShapedOreRecipe!"); ++ } ++ ++ @Override ++ public void setKey(ResourceLocation key) { ++ ++ } + } diff --git a/patches/net/minecraftforge/oredict/ShapelessOreRecipe.java.patch b/patches/net/minecraftforge/oredict/ShapelessOreRecipe.java.patch new file mode 100644 index 00000000..9c8fd6ae --- /dev/null +++ b/patches/net/minecraftforge/oredict/ShapelessOreRecipe.java.patch @@ -0,0 +1,38 @@ +--- ../src-base/minecraft/net/minecraftforge/oredict/ShapelessOreRecipe.java ++++ ../src-work/minecraft/net/minecraftforge/oredict/ShapelessOreRecipe.java +@@ -42,6 +42,7 @@ + import com.google.gson.JsonElement; + import com.google.gson.JsonObject; + import com.google.gson.JsonParseException; ++import org.bukkit.inventory.Recipe; + + public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl implements IRecipe + { +@@ -138,9 +139,9 @@ + } + + @Override +- public boolean canFit(int p_194133_1_, int p_194133_2_) ++ public boolean canFit(int width, int height) + { +- return p_194133_1_ * p_194133_2_ >= this.input.size(); ++ return width * height >= this.input.size(); + } + + public static ShapelessOreRecipe factory(JsonContext context, JsonObject json) +@@ -157,4 +158,15 @@ + ItemStack itemstack = CraftingHelper.getItemStack(JsonUtils.getJsonObject(json, "result"), context); + return new ShapelessOreRecipe(group.isEmpty() ? null : new ResourceLocation(group), ings, itemstack); + } ++ ++ ++ @Override ++ public Recipe toBukkitRecipe() { ++ throw new UnsupportedOperationException("Cannot get bukkit-recipe from ShapelessOreRecipe!"); ++ } ++ ++ @Override ++ public void setKey(ResourceLocation key) { ++ ++ } + } diff --git a/patches/net/minecraftforge/registries/ForgeRegistry.java.patch b/patches/net/minecraftforge/registries/ForgeRegistry.java.patch new file mode 100644 index 00000000..4b3dfa6e --- /dev/null +++ b/patches/net/minecraftforge/registries/ForgeRegistry.java.patch @@ -0,0 +1,60 @@ +--- ../src-base/minecraft/net/minecraftforge/registries/ForgeRegistry.java ++++ ../src-work/minecraft/net/minecraftforge/registries/ForgeRegistry.java +@@ -32,6 +32,10 @@ + import javax.annotation.Nonnull; + import javax.annotation.Nullable; + ++import net.minecraft.block.Block; ++import net.minecraft.item.Item; ++import net.minecraftforge.common.util.EnumHelper; ++import net.minecraftforge.fml.relauncher.ReflectionHelper; + import org.apache.commons.lang3.Validate; + + import com.google.common.base.Preconditions; +@@ -55,6 +59,7 @@ + import net.minecraftforge.fml.common.InjectedModContainer; + import net.minecraftforge.fml.common.Loader; + import net.minecraftforge.fml.common.ModContainer; ++import org.bukkit.Material; + + public class ForgeRegistry> implements IForgeRegistryInternal, IForgeRegistryModifiable + { +@@ -188,8 +193,7 @@ + @Override + public ResourceLocation getKey(V value) + { +- ResourceLocation ret = this.names.inverse().get(value); +- return ret == null ? this.defaultKey : ret; ++ return this.names.inverse().get(value); + } + + @Override +@@ -429,9 +433,7 @@ + { + try + { +- Method method = BitSet.class.getDeclaredMethod("trimToSize"); +- method.setAccessible(true); +- method.invoke(this.availabilityMap); ++ ReflectionHelper.findMethod(BitSet.class, "trimToSize", null).invoke(this.availabilityMap); + } + catch (Exception e) + { +@@ -640,7 +642,7 @@ + public void loadIds(Map ids, Map overrides, Map missing, Map remapped, ForgeRegistry old, ResourceLocation name) + { + Map ovs = Maps.newHashMap(overrides); +- for (Map.Entry entry : ids.entrySet()) ++ for (Entry entry : ids.entrySet()) + { + ResourceLocation itemName = entry.getKey(); + int newId = entry.getValue(); +@@ -698,7 +700,7 @@ + ovs.remove(itemName); + } + +- for (Map.Entry entry : ovs.entrySet()) ++ for (Entry entry : ovs.entrySet()) + { + ResourceLocation itemName = entry.getKey(); + String owner = entry.getValue(); diff --git a/release/libraries.zip b/release/libraries.zip new file mode 100644 index 00000000..f4a9826a Binary files /dev/null and b/release/libraries.zip differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..93142792 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'kettle' + diff --git a/src/main/java/com/destroystokyo/paper/MCUtil.java b/src/main/java/com/destroystokyo/paper/MCUtil.java new file mode 100644 index 00000000..b66dcd26 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/MCUtil.java @@ -0,0 +1,214 @@ +package com.destroystokyo.paper; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import net.minecraft.entity.Entity; +import net.minecraft.init.Blocks; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityHopper; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.Waitable; +import org.spigotmc.AsyncCatcher; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +public final class MCUtil { + private static final Executor asyncExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build()); + + private MCUtil() {} + + /** + * Quickly generate a stack trace for current location + * + * @return Stacktrace + */ + public static String stack() { + return ExceptionUtils.getFullStackTrace(new Throwable()); + } + + /** + * Quickly generate a stack trace for current location with message + * + * @param str + * @return Stacktrace + */ + public static String stack(String str) { + return ExceptionUtils.getFullStackTrace(new Throwable(str)); + } + + /** + * Ensures the target code is running on the main thread + * @param reason + * @param run + * @param + * @return + */ + public static T ensureMain(String reason, Supplier run) { + if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServerCB().primaryThread) { + new IllegalStateException( "Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace(); + Waitable wait = new Waitable() { + @Override + protected T evaluate() { + return run.get(); + } + }; + MinecraftServer.getServerCB().processQueue.add(wait); + try { + return wait.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + } + return run.get(); + } + + /** + * Calculates distance between 2 entities + * @param e1 + * @param e2 + * @return + */ + public static double distance(Entity e1, Entity e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + + /** + * Calculates distance between 2 block positions + * @param e1 + * @param e2 + * @return + */ + public static double distance(BlockPos e1, BlockPos e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + /** + * Gets the distance between 2 positions + * @param x1 + * @param y1 + * @param z1 + * @param x2 + * @param y2 + * @param z2 + * @return + */ + public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) { + return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2)); + } + + /** + * Get's the distance squared between 2 entities + * @param e1 + * @param e2 + * @return + */ + public static double distanceSq(Entity e1, Entity e2) { + return distanceSq(e1.posX,e1.posY,e1.posZ, e2.posX,e2.posY,e2.posZ); + } + + /** + * Gets the distance sqaured between 2 block positions + * @param pos1 + * @param pos2 + * @return + */ + public static double distanceSq(BlockPos pos1, BlockPos pos2) { + return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ()); + } + + /** + * Gets the distance squared between 2 positions + * @param x1 + * @param y1 + * @param z1 + * @param x2 + * @param y2 + * @param z2 + * @return + */ + public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); + } + + /** + * Converts a NMS World/BlockPosition to Bukkit Location + * @param world + * @param x + * @param y + * @param z + * @return + */ + public static Location toLocation(World world, double x, double y, double z) { + return new Location(world.getWorld(), x, y, z); + } + + /** + * Converts a NMS World/BlockPosition to Bukkit Location + * @param world + * @param pos + * @return + */ + public static Location toLocation(World world, BlockPos pos) { + return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + + /** + * Converts an NMS entity's current location to a Bukkit Location + * @param entity + * @return + */ + public static Location toLocation(Entity entity) { + return new Location(entity.getEntityWorld().getWorld(), entity.posX, entity.posY, entity.posZ); + } + + public static BlockPos toBlockPosition(Location loc) { + return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + public static boolean isEdgeOfChunk(BlockPos pos) { + final int modX = pos.getX() & 15; + final int modZ = pos.getZ() & 15; + return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15); + } + + /** + * Posts a task to be executed asynchronously + * @param run + */ + public static void scheduleAsyncTask(Runnable run) { + asyncExecutor.execute(run); + } + + @Nullable + public static TileEntityHopper getHopper(World world, BlockPos pos) { + Chunk chunk = world.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4); + if (chunk != null && chunk.getBlockState(pos).getBlock() == Blocks.HOPPER) { + TileEntity tileEntity = chunk.getTileEntity(pos, Chunk.EnumCreateEntityType.IMMEDIATE); + if (tileEntity instanceof TileEntityHopper) { + return (TileEntityHopper) tileEntity; + } + } + return null; + } + + @Nonnull + public static World getNMSWorld(@Nonnull org.bukkit.World world) { + return ((CraftWorld) world).getHandle(); + } + + public static World getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { + return getNMSWorld(entity.getWorld()); + } +} diff --git a/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java new file mode 100644 index 00000000..1cc26f0e --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper; + +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerSchedulerException; +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.scheduler.CraftTask; + +/** + * Reporting wrapper to catch exceptions not natively + */ +public class ServerSchedulerReportingWrapper implements Runnable { + + private final CraftTask internalTask; + + public ServerSchedulerReportingWrapper(CraftTask internalTask) { + this.internalTask = Preconditions.checkNotNull(internalTask, "internalTask"); + } + + @Override + public void run() { + try { + internalTask.run(); + } catch (RuntimeException e) { + internalTask.getOwner().getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(e, internalTask)) + ); + throw e; + } catch (Throwable t) { + internalTask.getOwner().getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(t, internalTask)) + ); //Do not rethrow, since it is not permitted with Runnable#run + } + } + + public CraftTask getInternalTask() { + return internalTask; + } +} \ No newline at end of file diff --git a/src/main/java/com/destroystokyo/paper/Title.java b/src/main/java/com/destroystokyo/paper/Title.java new file mode 100644 index 00000000..2af975eb --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/Title.java @@ -0,0 +1,357 @@ +package com.destroystokyo.paper; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.entity.Player; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * Represents a title to may be sent to a {@link Player}. + * + *

A title can be sent without subtitle text.

+ */ +public final class Title { + + /** + * The default number of ticks for the title to fade in. + */ + public static final int DEFAULT_FADE_IN = 20; + /** + * The default number of ticks for the title to stay. + */ + public static final int DEFAULT_STAY = 200; + /** + * The default number of ticks for the title to fade out. + */ + public static final int DEFAULT_FADE_OUT = 20; + + private final BaseComponent[] title; + private final BaseComponent[] subtitle; + private final int fadeIn; + private final int stay; + private final int fadeOut; + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(BaseComponent title) { + this(title, null); + } + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(BaseComponent[] title) { + this(title, null); + } + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(String title) { + this(title, null); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(BaseComponent title, BaseComponent subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(BaseComponent[] title, BaseComponent[] subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(String title, String subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Creates a new title. + * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + * @throws IllegalArgumentException if any of the times are negative + */ + public Title(BaseComponent title, BaseComponent subtitle, int fadeIn, int stay, int fadeOut) { + this( + new BaseComponent[]{checkNotNull(title, "title")}, + subtitle == null ? null : new BaseComponent[]{subtitle}, + fadeIn, + stay, + fadeOut + ); + } + + /** + * Creates a new title. + * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + * @throws IllegalArgumentException if any of the times are negative + */ + public Title(BaseComponent[] title, BaseComponent[] subtitle, int fadeIn, int stay, int fadeOut) { + checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn); + checkArgument(stay >= 0, "Negative stay: %s", stay); + checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut); + this.title = checkNotNull(title, "title"); + this.subtitle = subtitle; + this.fadeIn = fadeIn; + this.stay = stay; + this.fadeOut = fadeOut; + } + + /** + * Creates a new title. + * + *

It is recommended to the {@link BaseComponent} constrctors.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + */ + public Title(String title, String subtitle, int fadeIn, int stay, int fadeOut) { + this( + TextComponent.fromLegacyText(checkNotNull(title, "title")), + subtitle == null ? null : TextComponent.fromLegacyText(subtitle), + fadeIn, + stay, + fadeOut + ); + } + + /** + * Gets the text of this title + * + * @return the text + */ + public BaseComponent[] getTitle() { + return this.title; + } + + /** + * Gets the text of this title's subtitle + * + * @return the text + */ + public BaseComponent[] getSubtitle() { + return this.subtitle; + } + + /** + * Gets the number of ticks to fade in. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to fade in + */ + public int getFadeIn() { + return this.fadeIn; + } + + /** + * Gets the number of ticks to stay. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to stay + */ + public int getStay() { + return this.stay; + } + + /** + * Gets the number of ticks to fade out. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to fade out + */ + public int getFadeOut() { + return this.fadeOut; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating titles + */ + public static final class Builder { + + private BaseComponent[] title; + private BaseComponent[] subtitle; + private int fadeIn = DEFAULT_FADE_IN; + private int stay = DEFAULT_STAY; + private int fadeOut = DEFAULT_FADE_OUT; + + /** + * Sets the title to the given text. + * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + public Builder title(BaseComponent title) { + return this.title(new BaseComponent[]{checkNotNull(title, "title")}); + } + + /** + * Sets the title to the given text. + * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + public Builder title(BaseComponent[] title) { + this.title = checkNotNull(title, "title"); + return this; + } + + /** + * Sets the title to the given text. + * + *

It is recommended to the {@link BaseComponent} methods.

+ * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + public Builder title(String title) { + return this.title(TextComponent.fromLegacyText(checkNotNull(title, "title"))); + } + + /** + * Sets the subtitle to the given text. + * + * @param subtitle the title text + * @return this builder instance + */ + public Builder subtitle(BaseComponent subtitle) { + return this.subtitle(subtitle == null ? null : new BaseComponent[]{subtitle}); + } + + /** + * Sets the subtitle to the given text. + * + * @param subtitle the title text + * @return this builder instance + */ + public Builder subtitle(BaseComponent[] subtitle) { + this.subtitle = subtitle; + return this; + } + + /** + * Sets the subtitle to the given text. + * + *

It is recommended to the {@link BaseComponent} methods.

+ * + * @param subtitle the title text + * @return this builder instance + */ + public Builder subtitle(String subtitle) { + return this.subtitle(subtitle == null ? null : TextComponent.fromLegacyText(subtitle)); + } + + /** + * Sets the number of ticks for the title to fade in + * + * @param fadeIn the number of ticks to fade in + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + public Builder fadeIn(int fadeIn) { + checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn); + this.fadeIn = fadeIn; + return this; + } + + + /** + * Sets the number of ticks for the title to stay. + * + * @param stay the number of ticks to stay + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + public Builder stay(int stay) { + checkArgument(stay >= 0, "Negative stay: %s", stay); + this.stay = stay; + return this; + } + + /** + * Sets the number of ticks for the title to fade out. + * + * @param fadeOut the number of ticks to fade out + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + public Builder fadeOut(int fadeOut) { + checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut); + this.fadeOut = fadeOut; + return this; + } + + /** + * Create a title based on the values in the builder. + * + * @return a title from the values in this builder + * @throws IllegalStateException if title isn't specified + */ + public Title build() { + checkState(title != null, "Title not specified"); + return new Title(this.title, this.subtitle, this.fadeIn, this.stay, this.fadeOut); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java b/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java new file mode 100644 index 00000000..6579ae99 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java @@ -0,0 +1,81 @@ +package com.destroystokyo.paper.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.potion.PotionEffect; + +/** + * Called when a beacon effect is being applied to a player. + */ +public class BeaconEffectEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private PotionEffect effect; + private Player player; + private boolean primary; + + public BeaconEffectEvent(Block block, PotionEffect effect, Player player, boolean primary) { + super(block); + this.effect = effect; + this.player = player; + this.primary = primary; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Gets the potion effect being applied. + * + * @return Potion effect + */ + public PotionEffect getEffect() { + return effect; + } + + /** + * Sets the potion effect that will be applied. + * + * @param effect Potion effect + */ + public void setEffect(PotionEffect effect) { + this.effect = effect; + } + + /** + * Gets the player who the potion effect is being applied to. + * + * @return Affected player + */ + public Player getPlayer() { + return player; + } + + /** + * Gets whether the effect is a primary beacon effect. + * + * @return true if this event represents a primary effect + */ + public boolean isPrimary() { + return primary; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java new file mode 100644 index 00000000..febb8526 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Fired when an Enderman determines if it should attack a player or not. + * Starts off cancelled if the player is wearing a pumpkin head or is not looking + * at the Enderman, according to Vanilla rules. + * + */ +public class EndermanAttackPlayerEvent extends EntityEvent implements Cancellable { + private final Player player; + + public EndermanAttackPlayerEvent(Enderman entity, Player player) { + super(entity); + this.player = player; + } + + /** + * The enderman considering attacking + * + * @return The enderman considering attacking + */ + @Override + public Enderman getEntity() { + return (Enderman) super.getEntity(); + } + + /** + * The player the Enderman is considering attacking + * + * @return The player the Enderman is considering attacking + */ + public Player getPlayer() { + return player; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * + * @return If cancelled, the enderman will not attack + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancels if the Enderman will attack this player + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java new file mode 100644 index 00000000..076a8e62 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java @@ -0,0 +1,81 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Enderman; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +public class EndermanEscapeEvent extends EntityEvent implements Cancellable { + private final Reason reason; + + public EndermanEscapeEvent(Enderman entity, Reason reason) { + super(entity); + this.reason = reason; + } + + @Override + public Enderman getEntity() { + return (Enderman) super.getEntity(); + } + + /** + * @return The reason the enderman is trying to escape + */ + public Reason getReason() { + return reason; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancels the escape. + * + * If this escape normally would of resulted in damage avoidance such as indirect, + * the enderman will now take damage. + * + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + public enum Reason { + /** + * The enderman has stopped attacking and ran away + */ + RUNAWAY, + /** + * The enderman has teleported away due to indirect damage (ranged) + */ + INDIRECT, + /** + * The enderman has teleported away due to a critical hit + */ + CRITICAL_HIT, + /** + * The enderman has teleported away due to the player staring at it during combat + */ + STARE, + /** + * Specific case for CRITICAL_HIT where the enderman is taking rain damage + */ + DROWN + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java new file mode 100644 index 00000000..79d79785 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Fired any time an entity is being added to the world for any reason. + * + * Not to be confused with {@link org.bukkit.event.entity.CreatureSpawnEvent} + * This will fire anytime a chunk is reloaded too. + */ +public class EntityAddToWorldEvent extends EntityEvent { + + public EntityAddToWorldEvent(Entity entity) { + super(entity); + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java new file mode 100644 index 00000000..ce5f21e0 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java @@ -0,0 +1,77 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.util.Vector; + +/** + * Fired when an Entity is knocked back by the hit of another Entity. The acceleration + * vector can be modified. If this event is cancelled, the entity is not knocked back. + * + */ +public class EntityKnockbackByEntityEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final Entity hitBy; + private final float knockbackStrength; + private final Vector acceleration; + private boolean cancelled = false; + + public EntityKnockbackByEntityEvent(LivingEntity entity, Entity hitBy, float knockbackStrength, Vector acceleration) { + super(entity); + this.hitBy = hitBy; + this.knockbackStrength = knockbackStrength; + this.acceleration = acceleration; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * @return the entity which was knocked back + */ + @Override + public LivingEntity getEntity() { + return (LivingEntity) super.getEntity(); + } + + /** + * @return the original knockback strength. + */ + public float getKnockbackStrength() { + return knockbackStrength; + } + + /** + * @return the Entity which hit + */ + public Entity getHitBy() { + return hitBy; + } + + /** + * @return the acceleration that will be applied + */ + public Vector getAcceleration() { + return acceleration; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java new file mode 100644 index 00000000..588448e5 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java @@ -0,0 +1,76 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Fired when an Entity decides to start moving towards a location. + * + * This event does not fire for the entities actual movement. Only when it + * is choosing to start moving to a location. + */ +public class EntityPathfindEvent extends EntityEvent implements Cancellable { + private final Entity targetEntity; + private final Location loc; + public EntityPathfindEvent(Entity entity, Location loc, Entity targetEntity) { + super(entity); + this.targetEntity = targetEntity; + this.loc = loc; + } + + /** + * The Entity that is pathfinding. + * @return The Entity that is pathfinding. + */ + @Override + public Entity getEntity() { + return entity; + } + + /** + * If the Entity is trying to pathfind to an entity, this is the entity in relation. + * + * Otherwise this will return null. + * + * @return The entity target or null + */ + public Entity getTargetEntity() { + return targetEntity; + } + + /** + * The Location of where the entity is about to move to. + * + * Note that if the target happened to of been an entity + * @return Location of where the entity is trying to pathfind to. + */ + public Location getLoc() { + return loc; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java new file mode 100644 index 00000000..26722173 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java @@ -0,0 +1,26 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Fired any time an entity is being removed from a world for any reason + */ +public class EntityRemoveFromWorldEvent extends EntityEvent { + + public EntityRemoveFromWorldEvent(Entity entity) { + super(entity); + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java new file mode 100644 index 00000000..80899ecb --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.block.EndGateway; +import org.bukkit.entity.Entity; +import org.bukkit.event.entity.EntityTeleportEvent; + +/** + * Fired any time an entity attempts to teleport in an end gateway + */ +public class EntityTeleportEndGatewayEvent extends EntityTeleportEvent { + + private final EndGateway gateway; + + public EntityTeleportEndGatewayEvent(Entity what, Location from, Location to, EndGateway gateway) { + super(what, from, to); + this.gateway = gateway; + } + + /** + * The gateway triggering the teleport + * + * @return EndGateway used + */ + public EndGateway getGateway() { + return gateway; + } + +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java new file mode 100644 index 00000000..e2f9cae6 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java @@ -0,0 +1,65 @@ +package com.destroystokyo.paper.event.entity; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +import javax.annotation.Nonnull; + +/** + * Fired when lightning strikes an entity + */ +public class EntityZapEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final LightningStrike bolt; + private final Entity replacementEntity; + + public EntityZapEvent(final Entity entity, @Nonnull final LightningStrike bolt, @Nonnull final Entity replacementEntity) { + super(entity); + Validate.notNull(bolt); + Validate.notNull(replacementEntity); + this.bolt = bolt; + this.replacementEntity = replacementEntity; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the lightning bolt that is striking the entity. + * @return The lightning bolt responsible for this event + */ + @Nonnull + public LightningStrike getBolt() { + return bolt; + } + + /** + * Gets the entity that will replace the struck entity. + * @return The entity that will replace the struck entity + */ + @Nonnull + public Entity getReplacementEntity() { + return replacementEntity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java new file mode 100644 index 00000000..ca271fa1 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Fired anytime the server is about to merge 2 experience orbs into one + */ +public class ExperienceOrbMergeEvent extends EntityEvent implements Cancellable { + private final ExperienceOrb mergeTarget; + private final ExperienceOrb mergeSource; + + public ExperienceOrbMergeEvent(ExperienceOrb mergeTarget, ExperienceOrb mergeSource) { + super(mergeTarget); + this.mergeTarget = mergeTarget; + this.mergeSource = mergeSource; + } + + /** + * @return The orb that will absorb the other experience orb + */ + public ExperienceOrb getMergeTarget() { + return mergeTarget; + } + + /** + * @return The orb that is subject to being removed and merged into the target orb + */ + public ExperienceOrb getMergeSource() { + return mergeSource; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * @param cancel true if you wish to cancel this event, and prevent the orbs from merging + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java new file mode 100644 index 00000000..d3ec9de2 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java @@ -0,0 +1,61 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Fired when the server is calculating what chunks to try to spawn monsters in every Monster Spawn Tick event + */ +public class PlayerNaturallySpawnCreaturesEvent extends PlayerEvent implements Cancellable { + private byte radius; + + public PlayerNaturallySpawnCreaturesEvent(Player player, byte radius) { + super(player); + this.radius = radius; + } + + /** + * @return The radius of chunks around this player to be included in natural spawn selection + */ + public byte getSpawnRadius() { + return radius; + } + + /** + * @param radius The radius of chunks around this player to be included in natural spawn selection + */ + public void setSpawnRadius(byte radius) { + this.radius = radius; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return If this players chunks will be excluded from natural spawns + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * @param cancel true if you wish to cancel this event, and not include this players chunks for natural spawning + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java new file mode 100644 index 00000000..4a372772 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java @@ -0,0 +1,99 @@ +package com.destroystokyo.paper.event.entity; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.CreatureSpawnEvent; + +/** + * WARNING: This event only fires for a limited number of cases, and not for every case that CreatureSpawnEvent does. + * + * You should still listen to CreatureSpawnEvent as a backup, and only use this event as an "enhancement". + * The intent of this event is to improve server performance, so limited use cases. + * + * Currently: NATURAL and SPAWNER based reasons. Please submit a Pull Request for future additions. + * Also, Plugins that replace Entity Registrations with their own custom entities might not fire this event. + */ +public class PreCreatureSpawnEvent extends Event implements Cancellable { + private final Location location; + private final EntityType type; + private final CreatureSpawnEvent.SpawnReason reason; + private boolean shouldAbortSpawn; + + public PreCreatureSpawnEvent(Location location, EntityType type, CreatureSpawnEvent.SpawnReason reason) { + this.location = Preconditions.checkNotNull(location, "Location may not be null").clone(); + this.type = Preconditions.checkNotNull(type, "Type may not be null"); + this.reason = Preconditions.checkNotNull(reason, "Reason may not be null"); + } + + /** + * @return The location this creature is being spawned at + */ + public Location getSpawnLocation() { + return location; + } + + /** + * @return The type of creature being spawned + */ + public EntityType getType() { + return type; + } + + /** + * @return Reason this creature is spawning (ie, NATURAL vs SPAWNER) + */ + public CreatureSpawnEvent.SpawnReason getReason() { + return reason; + } + + /** + * @return If the spawn process should be aborted vs trying more attempts + */ + public boolean shouldAbortSpawn() { + return shouldAbortSpawn; + } + + /** + * Set this if you are more blanket blocking all types of these spawns, and wish to abort the spawn process from + * trying more attempts after this cancellation. + * + * @param shouldAbortSpawn Set if the spawn process should be aborted vs trying more attempts + */ + public void setShouldAbortSpawn(boolean shouldAbortSpawn) { + this.shouldAbortSpawn = shouldAbortSpawn; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return If the spawn of this creature is cancelled or not + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancelling this event is more effecient than cancelling CreatureSpawnEvent + * @param cancel true if you wish to cancel this event, and abort the spawn of this creature + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java new file mode 100644 index 00000000..62bcc036 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java @@ -0,0 +1,63 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Called when an projectile collides with an entity + *

+ * This event is called before {@link org.bukkit.event.entity.EntityDamageByEntityEvent}, and cancelling it will allow the projectile to continue flying + */ +public class ProjectileCollideEvent extends EntityEvent implements Cancellable { + private final Entity collidedWith; + + /** + * Get the entity the projectile collided with + * + * @return the entity collided with + */ + public Entity getCollidedWith() { + return collidedWith; + } + + public ProjectileCollideEvent(Projectile what, Entity collidedWith) { + super(what); + this.collidedWith = collidedWith; + } + + /** + * Get the projectile that collided + * + * @return the projectile that collided + */ + @Override + public Projectile getEntity() { + return (Projectile) super.getEntity(); + } + + private static final HandlerList handlerList = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlerList; + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java new file mode 100644 index 00000000..19b13521 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java @@ -0,0 +1,65 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Fired when a witch consumes the potion in their hand to buff themselves. + */ +public class WitchConsumePotionEvent extends EntityEvent implements Cancellable { + private ItemStack potion; + + public WitchConsumePotionEvent(Witch witch, ItemStack potion) { + super(witch); + this.potion = potion; + } + + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return the potion the witch will consume and have the effects applied. + */ + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion to be consumed and applied to the witch. + * @param potion The potion + */ + public void setPotion(ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return Event was cancelled or potion was null + */ + @Override + public boolean isCancelled() { + return cancelled || potion == null; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java new file mode 100644 index 00000000..6155d433 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java @@ -0,0 +1,74 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Material; +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; + +public class WitchReadyPotionEvent extends EntityEvent implements Cancellable { + private ItemStack potion; + + public WitchReadyPotionEvent(Witch witch, ItemStack potion) { + super(witch); + this.potion = potion; + } + + /** + * Fires thee event, returning the desired potion, or air of cancelled + * @param witch the witch whom is readying to use a potion + * @param potion the potion to be used + * @return The ItemStack to be used + */ + public static ItemStack process(Witch witch, ItemStack potion) { + WitchReadyPotionEvent event = new WitchReadyPotionEvent(witch, potion); + if (!event.callEvent() || event.getPotion() == null) { + return new ItemStack(Material.AIR); + } + return event.getPotion(); + } + + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return the potion the witch is readying to use + */ + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion the which is going to hold and use + * @param potion The potion + */ + public void setPotion(ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java new file mode 100644 index 00000000..80d09630 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java @@ -0,0 +1,75 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Fired when a witch throws a potion at a player + */ +public class WitchThrowPotionEvent extends EntityEvent implements Cancellable { + private final LivingEntity target; + private ItemStack potion; + + public WitchThrowPotionEvent(Witch witch, LivingEntity target, ItemStack potion) { + super(witch); + this.target = target; + this.potion = potion; + } + + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return The target of the potion + */ + public LivingEntity getTarget() { + return target; + } + + /** + * @return The potion the witch will throw at a player + */ + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion to be thrown at a player + * @param potion The potion + */ + public void setPotion(ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return Event was cancelled or potion was null + */ + @Override + public boolean isCancelled() { + return cancelled || potion == null; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java new file mode 100644 index 00000000..7f666544 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java @@ -0,0 +1,42 @@ +package com.destroystokyo.paper.event.executor; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +public class MethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; + + public MethodHandleEventExecutor(Class eventClass, MethodHandle handle) { + this.eventClass = eventClass; + this.handle = handle; + } + + public MethodHandleEventExecutor(Class eventClass, Method m) { + this.eventClass = eventClass; + try { + m.setAccessible(true); + this.handle = MethodHandles.lookup().unreflect(m); + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } + } + + @Override + public void execute(Listener listener, Event event) throws EventException { + if (!eventClass.isInstance(event)) { + return; + } + try { + handle.invoke(listener, event); + } catch (Throwable t) { + throw new EventException(t); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java new file mode 100644 index 00000000..5db6060c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java @@ -0,0 +1,40 @@ +package com.destroystokyo.paper.event.executor; + +import com.google.common.base.Preconditions; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class StaticMethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; + + public StaticMethodHandleEventExecutor(Class eventClass, Method m) { + Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m); + this.eventClass = eventClass; + try { + m.setAccessible(true); + this.handle = MethodHandles.lookup().unreflect(m); + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } + } + + @Override + public void execute(Listener listener, Event event) throws EventException { + if (!eventClass.isInstance(event)) { + return; + } + try { + handle.invoke(event); + } catch (Throwable t) { + throw new EventException(t); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java new file mode 100644 index 00000000..0284c11f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java @@ -0,0 +1,48 @@ +package com.destroystokyo.paper.event.executor.asm; + +import org.bukkit.plugin.EventExecutor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.V1_8; + +public class ASMEventExecutorGenerator { + public static byte[] generateEventExecutor(Method m, String name) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)}); + // Generate constructor + GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "", "()V", null, null), ACC_PUBLIC, "", "()V"); + methodGenerator.loadThis(); + methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); // Invoke the super class (Object) constructor + methodGenerator.returnValue(); + methodGenerator.endMethod(); + // Generate the execute method + methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V"); + methodGenerator.loadArg(0); + methodGenerator.checkCast(Type.getType(m.getDeclaringClass())); + methodGenerator.loadArg(1); + methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0])); + methodGenerator.visitMethodInsn(m.getDeclaringClass().isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface()); + if (m.getReturnType() != void.class) { + methodGenerator.pop(); + } + methodGenerator.returnValue(); + methodGenerator.endMethod(); + writer.visitEnd(); + return writer.toByteArray(); + } + + public static AtomicInteger NEXT_ID = new AtomicInteger(1); + public static String generateName() { + int id = NEXT_ID.getAndIncrement(); + return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java new file mode 100644 index 00000000..cc0f17f3 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java @@ -0,0 +1,30 @@ +package com.destroystokyo.paper.event.executor.asm; + +public interface ClassDefiner { + + /** + * Returns if the defined classes can bypass access checks + * + * @return if classes bypass access checks + */ + default boolean isBypassAccessChecks() { + return false; + } + + /** + * Define a class + * + * @param parentLoader the parent classloader + * @param name the name of the class + * @param data the class data to load + * @return the defined class + * @throws ClassFormatError if the class data is invalid + * @throws NullPointerException if any of the arguments are null + */ + Class defineClass(ClassLoader parentLoader, String name, byte[] data); + + static ClassDefiner getInstance() { + return SafeClassDefiner.INSTANCE; + } + +} diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java new file mode 100644 index 00000000..a8e5317d --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java @@ -0,0 +1,60 @@ +package com.destroystokyo.paper.event.executor.asm; + +import com.google.common.base.Preconditions; +import com.google.common.collect.MapMaker; + +import java.util.concurrent.ConcurrentMap; + +public class SafeClassDefiner implements ClassDefiner { + /* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner(); + + private SafeClassDefiner() {} + + private final ConcurrentMap loaders = new MapMaker().weakKeys().makeMap(); + + @Override + public Class defineClass(ClassLoader parentLoader, String name, byte[] data) { + GeneratedClassLoader loader = loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new); + synchronized (loader.getClassLoadingLock(name)) { + Preconditions.checkState(!loader.hasClass(name), "%s already defined", name); + Class c = loader.define(name, data); + assert c.getName().equals(name); + return c; + } + } + + private static class GeneratedClassLoader extends ClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + protected GeneratedClassLoader(ClassLoader parent) { + super(parent); + } + + private Class define(String name, byte[] data) { + synchronized (getClassLoadingLock(name)) { + assert !hasClass(name); + Class c = defineClass(name, data, 0, data.length); + resolveClass(c); + return c; + } + } + + @Override + public Object getClassLoadingLock(String name) { + return super.getClassLoadingLock(name); + } + + public boolean hasClass(String name) { + synchronized (getClassLoadingLock(name)) { + try { + Class.forName(name); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java b/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java new file mode 100644 index 00000000..f0810611 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +public class IllegalPacketEvent extends PlayerEvent { + private final String type; + private final String ex; + private String kickMessage; + private boolean shouldKick = true; + + public IllegalPacketEvent(Player player, String type, String kickMessage, Exception e) { + super(player); + this.type = type; + this.kickMessage = kickMessage; + this.ex = e.getMessage(); + } + + public boolean isShouldKick() { + return shouldKick; + } + + public void setShouldKick(boolean shouldKick) { + this.shouldKick = shouldKick; + } + + public String getKickMessage() { + return kickMessage; + } + + public void setKickMessage(String kickMessage) { + this.kickMessage = kickMessage; + } + + public String getType() { + return type; + } + + public String getExceptionMessage() { + return ex; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public static void process(Player player, String type, String kickMessage, Exception exception) { + IllegalPacketEvent event = new IllegalPacketEvent(player, type, kickMessage, exception); + event.callEvent(); + if (event.shouldKick) { + player.kickPlayer(kickMessage); + } + Bukkit.getLogger().severe(player.getName() + "/" + type + ": " + exception.getMessage()); + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java new file mode 100644 index 00000000..70de24c3 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java @@ -0,0 +1,60 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.advancement.Advancement; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when a player is granted a criteria in an advancement. + */ +public class PlayerAdvancementCriterionGrantEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Advancement advancement; + private final String criterion; + private boolean cancel = false; + + public PlayerAdvancementCriterionGrantEvent(Player who, Advancement advancement, String criterion) { + super(who); + this.advancement = advancement; + this.criterion = criterion; + } + + /** + * Get the advancement which has been affected. + * + * @return affected advancement + */ + public Advancement getAdvancement() { + return advancement; + } + + /** + * Get the criterion which has been granted. + * + * @return granted criterion + */ + public String getCriterion() { + return criterion; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java new file mode 100644 index 00000000..4a6a64c5 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java @@ -0,0 +1,157 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.bukkit.Material.CHAINMAIL_BOOTS; +import static org.bukkit.Material.CHAINMAIL_CHESTPLATE; +import static org.bukkit.Material.CHAINMAIL_HELMET; +import static org.bukkit.Material.CHAINMAIL_LEGGINGS; +import static org.bukkit.Material.DIAMOND_BOOTS; +import static org.bukkit.Material.DIAMOND_CHESTPLATE; +import static org.bukkit.Material.DIAMOND_HELMET; +import static org.bukkit.Material.DIAMOND_LEGGINGS; +import static org.bukkit.Material.ELYTRA; +import static org.bukkit.Material.GOLD_BOOTS; +import static org.bukkit.Material.GOLD_CHESTPLATE; +import static org.bukkit.Material.GOLD_HELMET; +import static org.bukkit.Material.GOLD_LEGGINGS; +import static org.bukkit.Material.IRON_BOOTS; +import static org.bukkit.Material.IRON_CHESTPLATE; +import static org.bukkit.Material.IRON_HELMET; +import static org.bukkit.Material.IRON_LEGGINGS; +import static org.bukkit.Material.JACK_O_LANTERN; +import static org.bukkit.Material.LEATHER_BOOTS; +import static org.bukkit.Material.LEATHER_CHESTPLATE; +import static org.bukkit.Material.LEATHER_HELMET; +import static org.bukkit.Material.LEATHER_LEGGINGS; +import static org.bukkit.Material.PUMPKIN; + +/** + * Called when the player themselves change their armor items + *

+ * Not currently called for environmental factors though it MAY BE IN THE FUTURE + */ +public class PlayerArmorChangeEvent extends PlayerEvent { + private static final HandlerList HANDLERS = new HandlerList(); + + private final SlotType slotType; + private final ItemStack oldItem; + private final ItemStack newItem; + + public PlayerArmorChangeEvent(Player player, SlotType slotType, ItemStack oldItem, ItemStack newItem) { + super(player); + this.slotType = slotType; + this.oldItem = oldItem; + this.newItem = newItem; + } + + /** + * Gets the type of slot being altered. + * + * @return type of slot being altered + */ + @Nonnull + public SlotType getSlotType() { + return this.slotType; + } + + /** + * Gets the existing item that's being replaced + * + * @return old item + */ + @Nullable + public ItemStack getOldItem() { + return this.oldItem; + } + + /** + * Gets the new item that's replacing the old + * + * @return new item + */ + @Nullable + public ItemStack getNewItem() { + return this.newItem; + } + + @Override + public String toString() { + return "ArmorChangeEvent{" + "player=" + player + ", slotType=" + slotType + ", oldItem=" + oldItem + ", newItem=" + newItem + '}'; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public enum SlotType { + HEAD(DIAMOND_HELMET, GOLD_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, PUMPKIN, JACK_O_LANTERN), + CHEST(DIAMOND_CHESTPLATE, GOLD_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA), + LEGS(DIAMOND_LEGGINGS, GOLD_LEGGINGS, IRON_LEGGINGS, CHAINMAIL_LEGGINGS, LEATHER_LEGGINGS), + FEET(DIAMOND_BOOTS, GOLD_BOOTS, IRON_BOOTS, CHAINMAIL_BOOTS, LEATHER_BOOTS); + + private final Set mutableTypes = new HashSet<>(); + private Set immutableTypes; + + SlotType(Material... types) { + this.mutableTypes.addAll(Arrays.asList(types)); + } + + /** + * Gets an immutable set of all allowed material types that can be placed in an + * armor slot. + * + * @return immutable set of material types + */ + @Nonnull + public Set getTypes() { + if (immutableTypes == null) { + immutableTypes = Collections.unmodifiableSet(mutableTypes); + } + + return immutableTypes; + } + + /** + * Gets the type of slot via the specified material + * + * @param material material to get slot by + * @return slot type the material will go in, or null if it won't + */ + @Nullable + public static SlotType getByMaterial(Material material) { + for (SlotType slotType : values()) { + if (slotType.getTypes().contains(material)) { + return slotType; + } + } + return null; + } + + /** + * Gets whether or not this material can be equipped to a slot + * + * @param material material to check + * @return whether or not this material can be equipped + */ + public static boolean isEquipable(Material material) { + return getByMaterial(material) != null; + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java new file mode 100644 index 00000000..e44d03a2 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java @@ -0,0 +1,211 @@ +package com.destroystokyo.paper.event.player; + +import org.apache.commons.lang.Validate; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.UUID; + +/** + * This event is fired during a player handshake. + * + *

If there are no listeners listening to this event, the logic default + * to your server platform will be ran.

+ * + *

WARNING: TAMPERING WITH THIS EVENT CAN BE DANGEROUS

+ */ +public class PlayerHandshakeEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + private final String originalHandshake; + private boolean cancelled; + private String serverHostname; + private String socketAddressHostname; + private UUID uniqueId; + private String propertiesJson; + private boolean failed; + private String failMessage = "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"; + + /** + * Creates a new {@link PlayerHandshakeEvent}. + * + * @param originalHandshake the original handshake string + * @param cancelled if this event is enabled + */ + public PlayerHandshakeEvent(String originalHandshake, boolean cancelled) { + this.originalHandshake = originalHandshake; + this.cancelled = cancelled; + } + + /** + * Determines if this event is cancelled. + * + *

When this event is cancelled, custom handshake logic will not + * be processed.

+ * + * @return {@code true} if this event is cancelled, {@code false} otherwise + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Sets if this event is cancelled. + * + *

When this event is cancelled, custom handshake logic will not + * be processed.

+ * + * @param cancelled {@code true} if this event is cancelled, {@code false} otherwise + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Gets the original handshake string. + * + * @return the original handshake string + */ + public String getOriginalHandshake() { + return this.originalHandshake; + } + + /** + * Gets the server hostname string. + * + *

This should not include the port.

+ * + * @return the server hostname string + */ + public String getServerHostname() { + return this.serverHostname; + } + + /** + * Sets the server hostname string. + * + *

This should not include the port.

+ * + * @param serverHostname the server hostname string + */ + public void setServerHostname(String serverHostname) { + this.serverHostname = serverHostname; + } + + /** + * Gets the socket address hostname string. + * + *

This should not include the port.

+ * + * @return the socket address hostname string + */ + public String getSocketAddressHostname() { + return this.socketAddressHostname; + } + + /** + * Sets the socket address hostname string. + * + *

This should not include the port.

+ * + * @param socketAddressHostname the socket address hostname string + */ + public void setSocketAddressHostname(String socketAddressHostname) { + this.socketAddressHostname = socketAddressHostname; + } + + /** + * Gets the unique id. + * + * @return the unique id + */ + public UUID getUniqueId() { + return this.uniqueId; + } + + /** + * Sets the unique id. + * + * @param uniqueId the unique id + */ + public void setUniqueId(UUID uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * Gets the profile properties. + * + *

This should be a valid JSON string.

+ * + * @return the profile properties, as JSON + */ + public String getPropertiesJson() { + return this.propertiesJson; + } + + /** + * Determines if authentication failed. + * + *

When {@code true}, the client connecting will be disconnected + * with the {@link #getFailMessage() fail message}.

+ * + * @return {@code true} if authentication failed, {@code false} otherwise + */ + public boolean isFailed() { + return this.failed; + } + + /** + * Sets if authentication failed and the client should be disconnected. + * + *

When {@code true}, the client connecting will be disconnected + * with the {@link #getFailMessage() fail message}.

+ * + * @param failed {@code true} if authentication failed, {@code false} otherwise + */ + public void setFailed(boolean failed) { + this.failed = failed; + } + + /** + * Sets the profile properties. + * + *

This should be a valid JSON string.

+ * + * @param propertiesJson the profile properties, as JSON + */ + public void setPropertiesJson(String propertiesJson) { + this.propertiesJson = propertiesJson; + } + + /** + * Gets the message to display to the client when authentication fails. + * + * @return the message to display to the client + */ + public String getFailMessage() { + return this.failMessage; + } + + /** + * Sets the message to display to the client when authentication fails. + * + * @param failMessage the message to display to the client + */ + public void setFailMessage(String failMessage) { + Validate.notEmpty(failMessage, "fail message cannot be null or empty"); + this.failMessage = failMessage; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java new file mode 100644 index 00000000..d1d6f33c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java @@ -0,0 +1,43 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +public class PlayerInitialSpawnEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private Location spawnLocation; + + public PlayerInitialSpawnEvent(final Player player, final Location spawnLocation) { + super(player); + this.spawnLocation = spawnLocation; + } + + /** + * Gets the current spawn location + * + * @return Location current spawn location + */ + public Location getSpawnLocation() { + return this.spawnLocation; + } + + /** + * Sets the new spawn location + * + * @param spawnLocation new location for the spawn + */ + public void setSpawnLocation(Location spawnLocation) { + this.spawnLocation = spawnLocation; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java new file mode 100644 index 00000000..9a257584 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java @@ -0,0 +1,103 @@ +package com.destroystokyo.paper.event.player; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when the server detects the player is jumping. + *

+ * Added to avoid the overhead and special case logic that many plugins use + * when checking for jumps via PlayerMoveEvent, this event is fired whenever + * the server detects that the player is jumping. + */ +public class PlayerJumpEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Location from; + private Location to; + + public PlayerJumpEvent(final Player player, final Location from, final Location to) { + super(player); + this.from = from; + this.to = to; + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a jump event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @return true if this event is cancelled + */ + @Override + public boolean isCancelled() { + return cancel; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a jump event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location this player jumped from + * + * @return Location the player jumped from + */ + public Location getFrom() { + return from; + } + + /** + * Sets the location to mark as where the player jumped from + * + * @param from New location to mark as the players previous location + */ + public void setFrom(Location from) { + validateLocation(from); + this.from = from; + } + + /** + * Gets the location this player jumped to + * + * This information is based on what the client sends, it typically + * has little relation to the arc of the jump at any given point. + * + * @return Location the player jumped to + */ + public Location getTo() { + return to; + } + + private void validateLocation(Location loc) { + Preconditions.checkArgument(loc != null, "Cannot use null location!"); + Preconditions.checkArgument(loc.getWorld() != null, "Cannot use location with null world!"); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java new file mode 100644 index 00000000..29dd763a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java @@ -0,0 +1,50 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when the locale of the player is changed. + * + * @deprecated Replaced by {@link org.bukkit.event.player.PlayerLocaleChangeEvent} upstream + */ +@Deprecated +public class PlayerLocaleChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String oldLocale; + private final String newLocale; + + public PlayerLocaleChangeEvent(final Player player, final String oldLocale, final String newLocale) { + super(player); + this.oldLocale = oldLocale; + this.newLocale = newLocale; + } + + /** + * Gets the locale the player switched from. + * + * @return player's old locale + */ + public String getOldLocale() { + return oldLocale; + } + + /** + * Gets the locale the player is changed to. + * + * @return player's new locale + */ + public String getNewLocale() { + return newLocale; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java new file mode 100644 index 00000000..83a1a5f0 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Fired when a player is attempting to pick up an experience orb + */ +public class PlayerPickupExperienceEvent extends PlayerEvent implements Cancellable { + private final ExperienceOrb experienceOrb; + + public PlayerPickupExperienceEvent(Player player, ExperienceOrb experienceOrb) { + super(player); + this.experienceOrb = experienceOrb; + } + + /** + * @return Returns the Orb that the player is picking up + */ + public ExperienceOrb getExperienceOrb() { + return experienceOrb; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * If true, Cancels picking up the experience orb, leaving it in the world + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java new file mode 100644 index 00000000..1ad2563d --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player is firing a bow and the server is choosing an arrow to use. + */ +public class PlayerReadyArrowEvent extends PlayerEvent implements Cancellable { + private final ItemStack bow; + private final ItemStack arrow; + + public PlayerReadyArrowEvent(Player player, ItemStack bow, ItemStack arrow) { + super(player); + this.bow = bow; + this.arrow = arrow; + } + + /** + * @return the player is using to fire the arrow + */ + public ItemStack getBow() { + return bow; + } + + /** + * @return the arrow that is attempting to be used + */ + public ItemStack getArrow() { + return arrow; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * Whether or not use of this arrow is cancelled. On cancel, the server will try the next arrow available and fire another event. + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancel use of this arrow. On cancel, the server will try the next arrow available and fire another event. + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java new file mode 100644 index 00000000..bdefbc9f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java @@ -0,0 +1,27 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Location; +import org.bukkit.block.EndGateway; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; + +/** + * Fired when a teleport is triggered for an End Gateway + */ +public class PlayerTeleportEndGatewayEvent extends PlayerTeleportEvent { + private final EndGateway gateway; + + public PlayerTeleportEndGatewayEvent(Player player, Location from, Location to, EndGateway gateway) { + super(player, from, to, PlayerTeleportEvent.TeleportCause.END_GATEWAY); + this.gateway = gateway; + } + + /** + * The gateway triggering the teleport + * + * @return EndGateway used + */ + public EndGateway getGateway() { + return gateway; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java new file mode 100644 index 00000000..70eeaf5c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java @@ -0,0 +1,42 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; + +public class PlayerUseUnknownEntityEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + private final int entityId; + private final boolean attack; + private final EquipmentSlot hand; + + public PlayerUseUnknownEntityEvent(Player who, int entityId, boolean attack, EquipmentSlot hand) { + super(who); + this.entityId = entityId; + this.attack = attack; + this.hand = hand; + } + + public int getEntityId() { + return this.entityId; + } + + public boolean isAttack() { + return this.attack; + } + + public EquipmentSlot getHand() { + return this.hand; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java b/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java new file mode 100644 index 00000000..30ff13e1 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import java.util.Set; + +/** + * Fired once a profiles additional properties (such as textures) has been filled + */ +public class FillProfileEvent extends Event { + private final PlayerProfile profile; + + public FillProfileEvent(@Nonnull PlayerProfile profile) { + super(!org.bukkit.Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The Profile that had properties filled + */ + @Nonnull + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Same as .getPlayerProfile().getProperties() + * + * @see PlayerProfile#getProperties() + * @return The new properties on the profile. + */ + public Set getProperties() { + return profile.getProperties(); + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java b/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java new file mode 100644 index 00000000..3b6995a7 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java @@ -0,0 +1,55 @@ +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.mojang.authlib.GameProfile; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; + +/** + * Allows a plugin to be notified anytime AFTER a Profile has been looked up from the Mojang API + * This is an opportunity to view the response and potentially cache things. + * + * No guarantees are made about thread execution context for this event. If you need to know, check + * event.isAsync() + */ +public class LookupProfileEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final PlayerProfile profile; + + public LookupProfileEvent(@Nonnull PlayerProfile profile) { + super(!Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The profile that was recently looked up. This profile can be mutated + * @deprecated will be removed with 1.13, use {@link #getPlayerProfile()} + */ + @Deprecated + @Nonnull + public GameProfile getProfile() { + return profile.getGameProfile(); + } + + /** + * @return The profile that was recently looked up. This profile can be mutated + */ + @Nonnull + public PlayerProfile getPlayerProfile() { + return profile; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java b/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java new file mode 100644 index 00000000..0df98379 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Fired when the server is requesting to fill in properties of an incomplete profile, such as textures. + * + * Allows plugins to pre populate cached properties and avoid a call to the Mojang API + */ +public class PreFillProfileEvent extends Event { + private final PlayerProfile profile; + + public PreFillProfileEvent(PlayerProfile profile) { + super(!org.bukkit.Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The profile that needs its properties filled + */ + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Sets the properties on the profile, avoiding the call to the Mojang API + * Same as .getPlayerProfile().setProperties(properties); + * + * @see PlayerProfile#setProperties(Collection) + * @param properties The properties to set/append + */ + public void setProperties(@Nonnull Collection properties) { + profile.setProperties(properties); + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java b/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java new file mode 100644 index 00000000..aa0666d5 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java @@ -0,0 +1,149 @@ +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.mojang.authlib.properties.Property; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Allows a plugin to intercept a Profile Lookup for a Profile by name + * + * At the point of event fire, the UUID and properties are unset. + * + * If a plugin sets the UUID, and optionally the properties, the API call to look up the profile may be skipped. + * + * No guarantees are made about thread execution context for this event. If you need to know, check + * event.isAsync() + */ +public class PreLookupProfileEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final String name; + private UUID uuid; + private Set properties = new HashSet<>(); + + public PreLookupProfileEvent(@Nonnull String name) { + super(!Bukkit.isPrimaryThread()); + this.name = name; + } + + /** + * @return Name of the profile + */ + @Nonnull + public String getName() { + return name; + } + + /** + * If this value is left null by the completion of the event call, then the server will + * trigger a call to the Mojang API to look up the UUID (Network Request), and subsequently, fire a + * {@link LookupProfileEvent} + * + * @return The UUID of the profile if it has already been provided by a plugin + */ + @Nullable + public UUID getUUID() { + return uuid; + } + + /** + * Sets the UUID for this player name. This will skip the initial API call to find the players UUID. + * + * However, if Profile Properties are needed by the server, you must also set them or else an API call might still be made. + * + * @param uuid the UUID to set for the profile or null to reset + */ + public void setUUID(@Nullable UUID uuid) { + this.uuid = uuid; + } + + /** + * Get the properties for this profile + * + * @return the property map to attach to the new {@link PlayerProfile} + * @deprecated will be removed with 1.13 Use {@link #getProfileProperties()} + */ + @Deprecated + @Nonnull + public Multimap getProperties() { + Multimap props = ArrayListMultimap.create(); + + for (ProfileProperty property : properties) { + props.put(property.getName(), new Property(property.getName(), property.getValue(), property.getSignature())); + } + return props; + } + + /** + * Completely replaces all Properties with the new provided properties + * @param properties the properties to set on the new profile + * @deprecated will be removed with 1.13 Use {@link #setProfileProperties(Set)} + */ + @Deprecated + public void setProperties(Multimap properties) { + this.properties = new HashSet<>(); + properties.values().forEach(property -> { + this.properties.add(new ProfileProperty(property.getName(), property.getValue(), property.getSignature())); + }); + } + + /** + * Adds additional properties, without removing the original properties + * @param properties the properties to add to the existing properties + * @deprecated will be removed with 1.13 use {@link #addProfileProperties(Set)} + */ + @Deprecated + public void addProperties(Multimap properties) { + properties.values().forEach(property -> { + this.properties.add(new ProfileProperty(property.getName(), property.getValue(), property.getSignature())); + }); + } + + /** + * @return The currently pending prepopulated properties. + * Any property in this Set will be automatically prefilled on this Profile + */ + public Set getProfileProperties() { + return this.properties; + } + + /** + * Clears any existing prepopulated properties and uses the supplied properties + * Any property in this Set will be automatically prefilled on this Profile + * @param properties The properties to add + */ + public void setProfileProperties(Set properties) { + this.properties = new HashSet<>(); + this.properties.addAll(properties); + } + + /** + * Adds any properties currently missing to the prepopulated properties set, replacing any that already were set. + * Any property in this Set will be automatically prefilled on this Profile + * @param properties The properties to add + */ + public void addProfileProperties(Set properties) { + this.properties.addAll(properties); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java b/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java new file mode 100644 index 00000000..199daf2b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017 - Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.mojang.authlib.GameProfile; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Fires when the server needs to verify if a player is whitelisted. + * + * Plugins may override/control the servers whitelist with this event, + * and dynamically change the kick message. + * + */ +public class ProfileWhitelistVerifyEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private final PlayerProfile profile; + private final boolean whitelistEnabled; + private boolean whitelisted; + private final boolean isOp; + private String kickMessage; + + public ProfileWhitelistVerifyEvent(final PlayerProfile profile, boolean whitelistEnabled, boolean whitelisted, boolean isOp, String kickMessage) { + this.profile = profile; + this.whitelistEnabled = whitelistEnabled; + this.whitelisted = whitelisted; + this.isOp = isOp; + this.kickMessage = kickMessage; + } + + /** + * @return the currently planned message to send to the user if they are not whitelisted + */ + public String getKickMessage() { + return kickMessage; + } + + /** + * @param kickMessage The message to send to the player on kick if not whitelisted. May set to null to use the server configured default + */ + public void setKickMessage(String kickMessage) { + this.kickMessage = kickMessage; + } + + /** + * @return the gameprofile of the player trying to connect + * @deprecated Will be removed in 1.13, use #{@link #getPlayerProfile()} + */ + @Deprecated + public GameProfile getProfile() { + return profile.getGameProfile(); + } + + /** + * @return The profile of the player trying to connect + */ + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * @return Whether the player is whitelisted to play on this server (whitelist may be off is why its true) + */ + public boolean isWhitelisted() { + return whitelisted; + } + + /** + * Changes the players whitelisted state. false will deny the login + * @param whitelisted The new whitelisted state + */ + public void setWhitelisted(boolean whitelisted) { + this.whitelisted = whitelisted; + } + + /** + * @return if the player obtained whitelist status by having op + */ + public boolean isOp() { + return isOp; + } + + /** + * @return if the server even has whitelist on + */ + public boolean isWhitelistEnabled() { + return whitelistEnabled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java b/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java new file mode 100644 index 00000000..7561124a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.server; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.ArrayList; +import java.util.List; + +/** + * Allows plugins to compute tab completion results asynchronously. If this event provides completions, then the standard synchronous process will not be fired to populate the results. However, the synchronous TabCompleteEvent will fire with the Async results. + * + * Only 1 process will be allowed to provide completions, the Async Event, or the standard process. + */ +public class AsyncTabCompleteEvent extends Event implements Cancellable { + private final CommandSender sender; + private final String buffer; + private final boolean isCommand; + private final Location loc; + private List completions; + private boolean cancelled; + private boolean handled = false; + private boolean fireSyncHandler = true; + + public AsyncTabCompleteEvent(CommandSender sender, List completions, String buffer, boolean isCommand, Location loc) { + super(true); + this.sender = sender; + this.completions = completions; + this.buffer = buffer; + this.isCommand = isCommand; + this.loc = loc; + } + + /** + * Get the sender completing this command. + * + * @return the {@link CommandSender} instance + */ + public CommandSender getSender() { + return sender; + } + + /** + * The list of completions which will be offered to the sender, in order. + * This list is mutable and reflects what will be offered. + * + * If this collection is not empty after the event is fired, then + * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @return a list of offered completions + */ + public List getCompletions() { + return completions; + } + + /** + * Set the completions offered, overriding any already set. + * If this collection is not empty after the event is fired, then + * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * The passed collection will be cloned to a new List. You must call {{@link #getCompletions()}} to mutate from here + * + * @param completions the new completions + */ + public void setCompletions(List completions) { + Validate.notNull(completions); + this.completions = new ArrayList<>(completions); + } + + /** + * Return the entire buffer which formed the basis of this completion. + * + * @return command buffer, as entered + */ + public String getBuffer() { + return buffer; + } + + /** + * @return True if it is a command being tab completed, false if it is a chat message. + */ + public boolean isCommand() { + return isCommand; + } + + /** + * @return The position looked at by the sender, or null if none + */ + public Location getLocation() { + return loc; + } + + /** + * If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @return Is completions considered handled. Always true if completions is not empty. + */ + public boolean isHandled() { + return !completions.isEmpty() || handled; + } + + /** + * Sets whether or not to consider the completion request handled. + * If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @param handled if this completion should be marked as being handled + */ + public void setHandled(boolean handled) { + this.handled = handled; + } + + private static final HandlerList handlers = new HandlerList(); + + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Will provide no completions, and will not fire the synchronous process + * @param cancelled true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java new file mode 100644 index 00000000..327ff335 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java @@ -0,0 +1,338 @@ +package com.destroystokyo.paper.event.server; + +import com.destroystokyo.paper.network.StatusClient; +import com.destroystokyo.paper.profile.PlayerProfile; +import com.google.common.base.Strings; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.util.CachedServerIcon; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import static java.util.Objects.requireNonNull; + +/** + * Extended version of {@link ServerListPingEvent} that allows full control + * of the response sent to the client. + */ +public class PaperServerListPingEvent extends ServerListPingEvent implements Cancellable { + + @Nonnull private final StatusClient client; + + private int numPlayers; + private boolean hidePlayers; + @Nonnull private final List playerSample = new ArrayList<>(); + + @Nonnull private String version; + private int protocolVersion; + + @Nullable private CachedServerIcon favicon; + + private boolean cancelled; + + private boolean originalPlayerCount = true; + private Object[] players; + + public PaperServerListPingEvent(@Nonnull StatusClient client, String motd, int numPlayers, int maxPlayers, + @Nonnull String version, int protocolVersion, @Nullable CachedServerIcon favicon) { + super(client.getAddress().getAddress(), motd, numPlayers, maxPlayers); + this.client = client; + this.numPlayers = numPlayers; + this.version = version; + this.protocolVersion = protocolVersion; + setServerIcon(favicon); + } + + /** + * Returns the {@link StatusClient} pinging the server. + * + * @return The client + */ + @Nonnull + public StatusClient getClient() { + return this.client; + } + + /** + * {@inheritDoc} + * + *

Returns {@code -1} if players are hidden using + * {@link #shouldHidePlayers()}.

+ */ + @Override + public int getNumPlayers() { + if (this.hidePlayers) { + return -1; + } + + return this.numPlayers; + } + + /** + * Sets the number of players displayed in the server list. + * + *

Note that this won't have any effect if {@link #shouldHidePlayers()} + * is enabled.

+ * + * @param numPlayers The number of online players + */ + public void setNumPlayers(int numPlayers) { + if (this.numPlayers != numPlayers) { + this.numPlayers = numPlayers; + this.originalPlayerCount = false; + } + } + + /** + * {@inheritDoc} + * + *

Returns {@code -1} if players are hidden using + * {@link #shouldHidePlayers()}.

+ */ + @Override + public int getMaxPlayers() { + if (this.hidePlayers) { + return -1; + } + + return super.getMaxPlayers(); + } + + /** + * Returns whether all player related information is hidden in the server + * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} + * and {@link #getPlayerSample()} to be skipped in the response. + * + *

The Vanilla Minecraft client will display the player count as {@code ???} + * when this option is enabled.

+ * + * @return {@code true} if the player count is hidden + */ + public boolean shouldHidePlayers() { + return hidePlayers; + } + + /** + * Sets whether all player related information is hidden in the server + * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} + * and {@link #getPlayerSample()} to be skipped in the response. + * + *

The Vanilla Minecraft client will display the player count as {@code ???} + * when this option is enabled.

+ * + * @param hidePlayers {@code true} if the player count should be hidden + */ + public void setHidePlayers(boolean hidePlayers) { + this.hidePlayers = hidePlayers; + } + + /** + * Returns a mutable list of {@link PlayerProfile} that will be displayed + * as online players on the client. + * + *

The Vanilla Minecraft client will display them when hovering the + * player count with the mouse.

+ * + * @return The mutable player sample list + */ + @Nonnull + public List getPlayerSample() { + return this.playerSample; + } + + /** + * Returns the version that will be sent as server version on the client. + * + * @return The server version + */ + @Nonnull + public String getVersion() { + return version; + } + + /** + * Sets the version that will be sent as server version to the client. + * + * @param version The server version + */ + public void setVersion(@Nonnull String version) { + this.version = requireNonNull(version, "version"); + } + + /** + * Returns the protocol version that will be sent as the protocol version + * of the server to the client. + * + * @return The protocol version of the server + */ + public int getProtocolVersion() { + return protocolVersion; + } + + /** + * Sets the protocol version that will be sent as the protocol version + * of the server to the client. + * + * @param protocolVersion The protocol version of the server + */ + public void setProtocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + } + + /** + * Gets the server icon sent to the client. + * + * @return The icon to send to the client, or {@code null} for none + */ + @Nullable + public CachedServerIcon getServerIcon() { + return this.favicon; + } + + /** + * Sets the server icon sent to the client. + * + * @param icon The icon to send to the client, or {@code null} for none + */ + @Override + public void setServerIcon(@Nullable CachedServerIcon icon) { + if (icon != null && icon.isEmpty()) { + // Represent empty icons as null + icon = null; + } + + this.favicon = icon; + } + + /** + * {@inheritDoc} + * + *

Cancelling this event will cause the connection to be closed immediately, + * without sending a response to the client.

+ */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * {@inheritDoc} + * + *

Cancelling this event will cause the connection to be closed immediately, + * without sending a response to the client.

+ */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * {@inheritDoc} + * + *

Note: For compatibility reasons, this method will return all + * online players, not just the ones referenced in {@link #getPlayerSample()}. + * Removing a player will:

+ * + *
    + *
  • Decrement the online player count (if and only if) the player + * count wasn't changed by another plugin before.
  • + *
  • Remove all entries from {@link #getPlayerSample()} that refer to + * the removed player (based on their {@link UUID}).
  • + *
+ */ + @Nonnull + @Override + public Iterator iterator() { + if (this.players == null) { + this.players = getOnlinePlayers(); + } + + return new PlayerIterator(); + } + + protected Object[] getOnlinePlayers() { + return Bukkit.getOnlinePlayers().toArray(); + } + + protected Player getBukkitPlayer(Object player) { + return (Player) player; + } + + private final class PlayerIterator implements Iterator { + + private int next; + private int current; + @Nullable private Player player; + + @Override + public boolean hasNext() { + for (; this.next < players.length; this.next++) { + if (players[this.next] != null) { + return true; + } + } + + return false; + } + + @Override + public Player next() { + if (!hasNext()) { + this.player = null; + throw new NoSuchElementException(); + } + + this.current = this.next++; + return this.player = getBukkitPlayer(players[this.current]); + } + + @Override + public void remove() { + if (this.player == null) { + throw new IllegalStateException(); + } + + UUID uniqueId = this.player.getUniqueId(); + this.player = null; + + // Remove player from iterator + players[this.current] = null; + + // Remove player from sample + getPlayerSample().removeIf(p -> uniqueId.equals(p.getId())); + + // Decrement player count + if (originalPlayerCount) { + numPlayers--; + } + } + } + + // TODO: Remove in 1.13 + + @Deprecated + public List getSampleText() { + List sampleText = new ArrayList<>(); + for (PlayerProfile profile : getPlayerSample()) { + sampleText.add(Strings.nullToEmpty(profile.getName())); + } + return sampleText; + } + + @Deprecated + public void setSampleText(List sample) { + getPlayerSample().clear(); + for (String name : sample) { + getPlayerSample().add(Bukkit.createProfile(name)); + } + } + +} diff --git a/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java b/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java new file mode 100644 index 00000000..3b9aef9b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java @@ -0,0 +1,36 @@ +package com.destroystokyo.paper.event.server; + +import com.destroystokyo.paper.exception.ServerException; +import com.google.common.base.Preconditions; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called whenever an exception is thrown in a recoverable section of the server. + */ +public class ServerExceptionEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private ServerException exception; + + public ServerExceptionEvent(ServerException exception) { + this.exception = Preconditions.checkNotNull(exception, "exception"); + } + + /** + * Gets the wrapped exception that was thrown. + * + * @return Exception thrown + */ + public ServerException getException() { + return exception; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java b/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java new file mode 100644 index 00000000..6fb39af0 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a command throws an exception + */ +public class ServerCommandException extends ServerException { + + private final Command command; + private final CommandSender commandSender; + private final String[] arguments; + + public ServerCommandException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + public ServerCommandException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + protected ServerCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + /** + * Gets the command which threw the exception + * + * @return exception throwing command + */ + public Command getCommand() { + return command; + } + + /** + * Gets the command sender which executed the command request + * + * @return command sender of exception thrown command request + */ + public CommandSender getCommandSender() { + return commandSender; + } + + /** + * Gets the arguments which threw the exception for the command + * + * @return arguments of exception thrown command request + */ + public String[] getArguments() { + return arguments; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java b/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java new file mode 100644 index 00000000..fc3863b6 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java @@ -0,0 +1,52 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Exception thrown when a server event listener throws an exception + */ +public class ServerEventException extends ServerPluginException { + + private final Listener listener; + private final Event event; + + public ServerEventException(String message, Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + public ServerEventException(Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + protected ServerEventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + /** + * Gets the listener which threw the exception + * + * @return event listener + */ + public Listener getListener() { + return listener; + } + + /** + * Gets the event which caused the exception + * + * @return event + */ + public Event getEvent() { + return event; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerException.java b/src/main/java/com/destroystokyo/paper/exception/ServerException.java new file mode 100644 index 00000000..c06ea394 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerException.java @@ -0,0 +1,23 @@ +package com.destroystokyo.paper.exception; + +/** + * Wrapper exception for all exceptions that are thrown by the server. + */ +public class ServerException extends Exception { + + public ServerException(String message) { + super(message); + } + + public ServerException(String message, Throwable cause) { + super(message, cause); + } + + public ServerException(Throwable cause) { + super(cause); + } + + protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java b/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java new file mode 100644 index 00000000..d9599a5b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java @@ -0,0 +1,34 @@ +package com.destroystokyo.paper.exception; + +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import org.bukkit.Bukkit; + +/** + * Thrown when the internal server throws a recoverable exception. + */ +public class ServerInternalException extends ServerException { + + public ServerInternalException(String message) { + super(message); + } + + public ServerInternalException(String message, Throwable cause) { + super(message, cause); + } + + public ServerInternalException(Throwable cause) { + super(cause); + } + + protected ServerInternalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public static void reportInternalException(Throwable cause) { + try { + Bukkit.getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(cause))); + } catch (Throwable t) { + t.printStackTrace(); // Don't want to rethrow! + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java b/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java new file mode 100644 index 00000000..f016ba3b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java @@ -0,0 +1,20 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.plugin.Plugin; + +/** + * Thrown whenever there is an exception with any enabling or disabling of plugins. + */ +public class ServerPluginEnableDisableException extends ServerPluginException { + public ServerPluginEnableDisableException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause, responsiblePlugin); + } + + public ServerPluginEnableDisableException(Throwable cause, Plugin responsiblePlugin) { + super(cause, responsiblePlugin); + } + + protected ServerPluginEnableDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + } +} \ No newline at end of file diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java b/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java new file mode 100644 index 00000000..be3f92e3 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java @@ -0,0 +1,36 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Wrapper exception for all cases to which a plugin can be immediately blamed for + */ +public class ServerPluginException extends ServerException { + public ServerPluginException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + public ServerPluginException(Throwable cause, Plugin responsiblePlugin) { + super(cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + protected ServerPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + private final Plugin responsiblePlugin; + + /** + * Gets the plugin which is directly responsible for the exception being thrown + * + * @return plugin which is responsible for the exception throw + */ + public Plugin getResponsiblePlugin() { + return responsiblePlugin; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java b/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java new file mode 100644 index 00000000..f58d9065 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when an incoming plugin message channel throws an exception + */ +public class ServerPluginMessageException extends ServerPluginException { + + private final Player player; + private final String channel; + private final byte[] data; + + public ServerPluginMessageException(String message, Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + public ServerPluginMessageException(Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + protected ServerPluginMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + /** + * Gets the channel to which the error occurred from recieving data from + * + * @return exception channel + */ + public String getChannel() { + return channel; + } + + /** + * Gets the data to which the error occurred from + * + * @return exception data + */ + public byte[] getData() { + return data; + } + + /** + * Gets the player which the plugin message causing the exception originated from + * + * @return exception player + */ + public Player getPlayer() { + return player; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java b/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java new file mode 100644 index 00000000..2d0b2d4a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java @@ -0,0 +1,37 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.scheduler.BukkitTask; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a plugin's scheduler fails with an exception + */ +public class ServerSchedulerException extends ServerPluginException { + + private final BukkitTask task; + + public ServerSchedulerException(String message, Throwable cause, BukkitTask task) { + super(message, cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + public ServerSchedulerException(Throwable cause, BukkitTask task) { + super(cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + protected ServerSchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BukkitTask task) { + super(message, cause, enableSuppression, writableStackTrace, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + /** + * Gets the task which threw the exception + * + * @return exception throwing task + */ + public BukkitTask getTask() { + return task; + } +} diff --git a/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java b/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java new file mode 100644 index 00000000..5582999f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java @@ -0,0 +1,22 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +/** + * Called when a tab-complete request throws an exception + */ +public class ServerTabCompleteException extends ServerCommandException { + + public ServerTabCompleteException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, command, commandSender, arguments); + } + + public ServerTabCompleteException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause, command, commandSender, arguments); + } + + protected ServerTabCompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace, command, commandSender, arguments); + } +} diff --git a/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java new file mode 100644 index 00000000..7e4acfff --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java @@ -0,0 +1,78 @@ +package com.destroystokyo.paper.inventory.meta; + +import org.bukkit.inventory.meta.ItemMeta; + +public interface ArmorStandMeta extends ItemMeta { + + /** + * Gets whether the ArmorStand should be invisible when spawned + * + * @return true if this should be invisible + */ + boolean isInvisible(); + + /** + * Gets whether this ArmorStand should have no base plate when spawned + * + * @return true if it will not have a base plate + */ + boolean hasNoBasePlate(); + + /** + * Gets whether this ArmorStand should show arms when spawned + * + * @return true if it will show arms + */ + boolean shouldShowArms(); + + /** + * Gets whether this ArmorStand will be small when spawned + * + * @return true if it will be small + */ + boolean isSmall(); + + /** + * Gets whether this ArmorStand will be a marker when spawned + * The exact details of this flag are an implementation detail + * + * @return true if it will be a marker + */ + boolean isMarker(); + + /** + * Sets that this ArmorStand should be invisible when spawned + * + * @param invisible true if set invisible + */ + void setInvisible(boolean invisible); + + /** + * Sets that this ArmorStand should have no base plate when spawned + * + * @param noBasePlate true if no base plate + */ + void setNoBasePlate(boolean noBasePlate); + + /** + * Sets that this ArmorStand should show arms when spawned + * + * @param showArms true if show arms + */ + void setShowArms(boolean showArms); + + /** + * Sets that this ArmorStand should be small when spawned + * + * @param small true if small + */ + void setSmall(boolean small); + + /** + * Sets that this ArmorStand should be a marker when spawned + * The exact details of this flag are an implementation detail + * + * @param marker true if a marker + */ + void setMarker(boolean marker); +} diff --git a/src/main/java/com/destroystokyo/paper/network/NetworkClient.java b/src/main/java/com/destroystokyo/paper/network/NetworkClient.java new file mode 100644 index 00000000..6fa7efbb --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/NetworkClient.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper.network; + +import javax.annotation.Nullable; +import java.net.InetSocketAddress; + +/** + * Represents a client connected to the server. + */ +public interface NetworkClient { + + /** + * Returns the socket address of the client. + * + * @return The client's socket address + */ + InetSocketAddress getAddress(); + + /** + * Returns the protocol version of the client. + * + * @return The client's protocol version, or {@code -1} if unknown + * @see List of protocol + * version numbers + */ + int getProtocolVersion(); + + /** + * Returns the virtual host the client is connected to. + * + *

The virtual host refers to the hostname/port the client used to + * connect to the server.

+ * + * @return The client's virtual host, or {@code null} if unknown + */ + @Nullable + InetSocketAddress getVirtualHost(); + +} diff --git a/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java new file mode 100644 index 00000000..4306de56 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java @@ -0,0 +1,72 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import net.minecraft.server.MinecraftServer; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.ChatColor; + +import javax.annotation.Nullable; +import java.net.InetSocketAddress; + +public final class PaperLegacyStatusClient implements StatusClient { + + private final InetSocketAddress address; + private final int protocolVersion; + @Nullable private final InetSocketAddress virtualHost; + + private PaperLegacyStatusClient(InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { + this.address = address; + this.protocolVersion = protocolVersion; + this.virtualHost = virtualHost; + } + + @Override + public InetSocketAddress getAddress() { + return this.address; + } + + @Override + public int getProtocolVersion() { + return this.protocolVersion; + } + + @Nullable + @Override + public InetSocketAddress getVirtualHost() { + return this.virtualHost; + } + + @Override + public boolean isLegacy() { + return true; + } + + public static PaperServerListPingEvent processRequest(MinecraftServer server, + InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { + + PaperServerListPingEvent event = new PaperServerListPingEventImpl(server, + new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null); + server.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return null; + } + + return event; + } + + public static String getMotd(PaperServerListPingEvent event) { + return getFirstLine(event.getMotd()); + } + + public static String getUnformattedMotd(PaperServerListPingEvent event) { + // Strip color codes and all other occurrences of the color char (because it's used as delimiter) + return getFirstLine(StringUtils.remove(ChatColor.stripColor(event.getMotd()), ChatColor.COLOR_CHAR)); + } + + private static String getFirstLine(String s) { + int pos = s.indexOf('\n'); + return pos >= 0 ? s.substring(0, pos) : s; + } + +} diff --git a/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java new file mode 100644 index 00000000..0c69d634 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java @@ -0,0 +1,49 @@ +package com.destroystokyo.paper.network; + +import net.minecraft.network.NetworkManager; + +import javax.annotation.Nullable; +import java.net.InetSocketAddress; + +public class PaperNetworkClient implements NetworkClient { + + private final NetworkManager networkManager; + + PaperNetworkClient(NetworkManager networkManager) { + this.networkManager = networkManager; + } + + @Override + public InetSocketAddress getAddress() { + return (InetSocketAddress) this.networkManager.getRemoteAddress(); + } + + @Override + public int getProtocolVersion() { + return this.networkManager.protocolVersion; + } + + @Nullable + @Override + public InetSocketAddress getVirtualHost() { + return this.networkManager.virtualHost; + } + + public static InetSocketAddress prepareVirtualHost(String host, int port) { + int len = host.length(); + + // FML appends a marker to the host to recognize FML clients (\0FML\0) + int pos = host.indexOf('\0'); + if (pos >= 0) { + len = pos; + } + + // When clients connect with a SRV record, their host contains a trailing '.' + if (len > 0 && host.charAt(len - 1) == '.') { + len--; + } + + return InetSocketAddress.createUnresolved(host.substring(0, len), port); + } + +} diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java new file mode 100644 index 00000000..5f277673 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import org.bukkit.entity.Player; +import org.bukkit.util.CachedServerIcon; + +import javax.annotation.Nullable; + +class PaperServerListPingEventImpl extends PaperServerListPingEvent { + + private final MinecraftServer server; + + PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) { + super(client, server.getMOTD(), server.getCurrentPlayerCount(), server.getMaxPlayers(), + server.getServerModName() + ' ' + server.getMinecraftVersion(), protocolVersion, icon); + this.server = server; + } + + @Override + protected final Object[] getOnlinePlayers() { + return this.server.getPlayerList().playerEntityList.toArray(); + } + + @Override + protected final Player getBukkitPlayer(Object player) { + return ((EntityPlayerMP) player).getBukkitEntity(); + } + +} diff --git a/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java new file mode 100644 index 00000000..46e84ac6 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java @@ -0,0 +1,11 @@ +package com.destroystokyo.paper.network; + +import net.minecraft.network.NetworkManager; + +class PaperStatusClient extends PaperNetworkClient implements StatusClient { + + PaperStatusClient(NetworkManager networkManager) { + super(networkManager); + } + +} diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java new file mode 100644 index 00000000..441acddb --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java @@ -0,0 +1,111 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.profile.CraftPlayerProfile; +import com.destroystokyo.paper.profile.PlayerProfile; +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.ServerStatusResponse; +import net.minecraft.network.status.server.SPacketServerInfo; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.text.TextComponentString; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.UUID; + +public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { + + private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0]; + private static final UUID FAKE_UUID = new UUID(0, 0); + + private GameProfile[] originalSample; + + private StandardPaperServerListPingEventImpl(MinecraftServer server, NetworkManager networkManager, ServerStatusResponse ping) { + super(server, new PaperStatusClient(networkManager), ping.getVersion() != null ? ping.getVersion().getProtocol() : -1, server.server.getServerIcon()); + this.originalSample = ping.getPlayers() == null ? null : ping.getPlayers().getPlayers(); // GH-1473 - pre-tick race condition NPE + } + + @Nonnull + @Override + public List getPlayerSample() { + List sample = super.getPlayerSample(); + + if (this.originalSample != null) { + for (GameProfile profile : this.originalSample) { + sample.add(CraftPlayerProfile.asBukkitCopy(profile)); + } + this.originalSample = null; + } + + return sample; + } + + private GameProfile[] getPlayerSampleHandle() { + if (this.originalSample != null) { + return this.originalSample; + } + + List entries = super.getPlayerSample(); + if (entries.isEmpty()) { + return EMPTY_PROFILES; + } + + GameProfile[] profiles = new GameProfile[entries.size()]; + for (int i = 0; i < profiles.length; i++) { + /* + * Avoid null UUIDs/names since that will make the response invalid + * on the client. + * Instead, fall back to a fake/empty UUID and an empty string as name. + * This can be used to create custom lines in the player list that do not + * refer to a specific player. + */ + + PlayerProfile profile = entries.get(i); + if (profile.getId() != null && profile.getName() != null) { + profiles[i] = CraftPlayerProfile.asAuthlib(profile); + } else { + profiles[i] = new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName())); + } + } + + return profiles; + } + + @SuppressWarnings("deprecation") + public static void processRequest(MinecraftServer server, NetworkManager networkManager) { + StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getServerStatusResponse()); + server.server.getPluginManager().callEvent(event); + + // Close connection immediately if event is cancelled + if (event.isCancelled()) { + networkManager.closeChannel(null); + return; + } + + // Setup response + ServerStatusResponse ping = new ServerStatusResponse(); + + // Description + ping.setServerDescription(new TextComponentString(event.getMotd())); + + // Players + if (!event.shouldHidePlayers()) { + ping.setPlayers(new ServerStatusResponse.Players(event.getMaxPlayers(), event.getNumPlayers())); + ping.getPlayers().setPlayers(event.getPlayerSampleHandle()); + } + + // Version + ping.setVersion(new ServerStatusResponse.Version(event.getVersion(), event.getProtocolVersion())); + + // Favicon + if (event.getServerIcon() != null) { + ping.setFavicon(event.getServerIcon().getData()); + } + + // Send response + networkManager.sendPacket(new SPacketServerInfo(ping)); + } + +} diff --git a/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/src/main/java/com/destroystokyo/paper/network/StatusClient.java new file mode 100644 index 00000000..ffda9f6a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/StatusClient.java @@ -0,0 +1,25 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; + +/** + * Represents a client requesting the current status from the server (e.g. from + * the server list). + * + * @see PaperServerListPingEvent + */ +public interface StatusClient extends NetworkClient { + + /** + * Returns whether the client is using an older version that doesn't + * support all of the features in {@link PaperServerListPingEvent}. + * + *

For Vanilla, this returns {@code true} for all clients older than 1.7.

+ * + * @return {@code true} if the client is using legacy ping + */ + default boolean isLegacy() { + return false; + } + +} diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java new file mode 100644 index 00000000..bd050b9b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java @@ -0,0 +1,285 @@ +package com.destroystokyo.paper.profile; + +import com.google.common.base.Charsets; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.PlayerProfileCache; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.spigotmc.SpigotConfig; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +public class CraftPlayerProfile implements PlayerProfile { + + private GameProfile profile; + private final PropertySet properties = new PropertySet(); + + public CraftPlayerProfile(CraftPlayer player) { + this.profile = player.getHandle().getGameProfile(); + } + + public CraftPlayerProfile(UUID id, String name) { + this.profile = new GameProfile(id, name); + } + + public CraftPlayerProfile(GameProfile profile) { + this.profile = profile; + } + + @Override + public boolean hasProperty(String property) { + return profile.getProperties().containsKey(property); + } + + @Override + public void setProperty(ProfileProperty property) { + String name = property.getName(); + PropertyMap properties = profile.getProperties(); + properties.removeAll(name); + properties.put(name, new Property(name, property.getValue(), property.getSignature())); + } + + @Override + public GameProfile getGameProfile() { + return profile; + } + + @Nullable + @Override + public UUID getId() { + return profile.getId(); + } + + @Override + public UUID setId(@Nullable UUID uuid) { + GameProfile prev = this.profile; + this.profile = new GameProfile(uuid, prev.getName()); + copyProfileProperties(prev, this.profile); + return prev.getId(); + } + + @Nullable + @Override + public String getName() { + return profile.getName(); + } + + @Override + public String setName(@Nullable String name) { + GameProfile prev = this.profile; + this.profile = new GameProfile(prev.getId(), name); + copyProfileProperties(prev, this.profile); + return prev.getName(); + } + + @Nonnull + @Override + public Set getProperties() { + return properties; + } + + @Override + public void setProperties(Collection properties) { + properties.forEach(this::setProperty); + } + + @Override + public void clearProperties() { + profile.getProperties().clear(); + } + + @Override + public boolean removeProperty(String property) { + return !profile.getProperties().removeAll(property).isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CraftPlayerProfile that = (CraftPlayerProfile) o; + return Objects.equals(profile, that.profile); + } + + @Override + public int hashCode() { + return profile.hashCode(); + } + + @Override + public String toString() { + return profile.toString(); + } + + @Override + public CraftPlayerProfile clone() { + CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); + clone.setProperties(getProperties()); + return clone; + } + + @Override + public boolean isComplete() { + return profile.isComplete(); + } + + @Override + public boolean completeFromCache() { + return completeFromCache(false); + } + + public boolean completeFromCache(boolean lookupName) { + if (profile.isComplete()) { + return true; + } + MinecraftServer server = MinecraftServer.getServerCB(); + String name = profile.getName(); + PlayerProfileCache userCache = server.getPlayerProfileCache(); + if (profile.getId() == null) { + final GameProfile profile; + boolean isOnlineMode = server.isServerInOnlineMode() || (SpigotConfig.bungee); + if (isOnlineMode) { + profile = lookupName ? userCache.getGameProfileForUsername(name) : userCache.getProfileIfCached(name); + } else { + // Make an OfflinePlayer using an offline mode UUID since the name has no profile + profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); + } + if (profile != null) { + this.profile = profile; + } + } + + if (profile.getName() == null) { + // If we need textures, skip this check, as we will get it below anyways. + GameProfile profile = userCache.getProfileByUUID(this.profile.getId()); + if (profile != null) { + this.profile = profile; + } + } + return this.profile.isComplete(); + } + + @Override + public boolean complete(boolean textures) { + MinecraftServer server = MinecraftServer.getServerCB(); + + boolean isOnlineMode = server.isServerInOnlineMode() || (SpigotConfig.bungee); + boolean isCompleteFromCache = this.completeFromCache(true); + if (isOnlineMode && (!isCompleteFromCache || textures && !hasTextures())) { + GameProfile result = server.getMinecraftSessionService().fillProfileProperties(profile, true); + if (result != null) { + this.profile = result; + } + } + return profile.isComplete() && (!isOnlineMode || !textures || hasTextures()); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target) { + PropertyMap sourceProperties = source.getProperties(); + if (sourceProperties.isEmpty()) { + return; + } + PropertyMap properties = target.getProperties(); + properties.clear(); + + for (Property property : sourceProperties.values()) { + properties.put(property.getName(), property); + } + } + + private static ProfileProperty toBukkit(Property property) { + return new ProfileProperty(property.getName(), property.getValue(), property.getSignature()); + } + + public static PlayerProfile asBukkitCopy(GameProfile gameProfile) { + CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName()); + copyProfileProperties(gameProfile, profile.profile); + return profile; + } + + public static PlayerProfile asBukkitMirror(GameProfile profile) { + return new CraftPlayerProfile(profile); + } + + public static Property asAuthlib(ProfileProperty property) { + return new Property(property.getName(), property.getValue(), property.getSignature()); + } + + public static GameProfile asAuthlibCopy(PlayerProfile profile) { + CraftPlayerProfile craft = ((CraftPlayerProfile) profile); + return asAuthlib(craft.clone()); + } + + public static GameProfile asAuthlib(PlayerProfile profile) { + CraftPlayerProfile craft = ((CraftPlayerProfile) profile); + return craft.getGameProfile(); + } + + private class PropertySet extends AbstractSet { + + @Override + @Nonnull + public Iterator iterator() { + return new ProfilePropertyIterator(profile.getProperties().values().iterator()); + } + + @Override + public int size() { + return profile.getProperties().size(); + } + + @Override + public boolean add(ProfileProperty property) { + setProperty(property); + return true; + } + + @Override + public boolean addAll(Collection c) { + //noinspection unchecked + setProperties((Collection) c); + return true; + } + + @Override + public boolean contains(Object o) { + return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); + } + + private class ProfilePropertyIterator implements Iterator { + private final Iterator iterator; + + ProfilePropertyIterator(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public ProfileProperty next() { + return toBukkit(iterator.next()); + } + + @Override + public void remove() { + iterator.remove(); + } + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java new file mode 100644 index 00000000..25836b97 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java @@ -0,0 +1,30 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Agent; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.UserAuthentication; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; + +import java.net.Proxy; + +public class PaperAuthenticationService extends YggdrasilAuthenticationService { + public PaperAuthenticationService(Proxy proxy, String clientToken) { + super(proxy, clientToken); + } + + @Override + public UserAuthentication createUserAuthentication(Agent agent) { + return new PaperUserAuthentication(this, agent); + } + + @Override + public MinecraftSessionService createMinecraftSessionService() { + return new PaperMinecraftSessionService(this); + } + + @Override + public GameProfileRepository createProfileRepository() { + return new PaperGameProfileRepository(this); + } +} diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java new file mode 100644 index 00000000..bb989431 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java @@ -0,0 +1,68 @@ +package com.destroystokyo.paper.profile; + +import com.destroystokyo.paper.event.profile.LookupProfileEvent; +import com.destroystokyo.paper.event.profile.PreLookupProfileEvent; +import com.google.common.collect.Sets; +import com.mojang.authlib.Agent; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.ProfileLookupCallback; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; + +import java.util.Set; + +public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + + public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService) { + super(authenticationService); + } + + @Override + public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { + Set unfoundNames = Sets.newHashSet(); + for (String name : names) { + PreLookupProfileEvent event = new PreLookupProfileEvent(name); + event.callEvent(); + if (event.getUUID() != null) { + // Plugin provided UUI, we can skip network call. + GameProfile gameprofile = new GameProfile(event.getUUID(), name); + // We might even have properties! + Set profileProperties = event.getProfileProperties(); + if (!profileProperties.isEmpty()) { + for (ProfileProperty property : profileProperties) { + gameprofile.getProperties().put(property.getName(), CraftPlayerProfile.asAuthlib(property)); + } + } + callback.onProfileLookupSucceeded(gameprofile); + } else { + unfoundNames.add(name); + } + } + + // Some things were not found.... Proceed to look up. + if (!unfoundNames.isEmpty()) { + String[] namesArr = unfoundNames.toArray(new String[unfoundNames.size()]); + super.findProfilesByNames(namesArr, agent, new PreProfileLookupCallback(callback)); + } + } + + private static class PreProfileLookupCallback implements ProfileLookupCallback { + private final ProfileLookupCallback callback; + + PreProfileLookupCallback(ProfileLookupCallback callback) { + this.callback = callback; + } + + @Override + public void onProfileLookupSucceeded(GameProfile gameProfile) { + PlayerProfile from = CraftPlayerProfile.asBukkitMirror(gameProfile); + new LookupProfileEvent(from).callEvent(); + callback.onProfileLookupSucceeded(gameProfile); + } + + @Override + public void onProfileLookupFailed(GameProfile gameProfile, Exception e) { + callback.onProfileLookupFailed(gameProfile, e); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java new file mode 100644 index 00000000..61cfdf73 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java @@ -0,0 +1,39 @@ +package com.destroystokyo.paper.profile; + +import com.destroystokyo.paper.event.profile.FillProfileEvent; +import com.destroystokyo.paper.event.profile.PreFillProfileEvent; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; + +import java.util.Map; + +public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { + protected PaperMinecraftSessionService(YggdrasilAuthenticationService authenticationService) { + super(authenticationService); + } + + @Override + public Map getTextures(GameProfile profile, boolean requireSecure) { + return super.getTextures(profile, requireSecure); + } + + @Override + public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { + CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile); + new PreFillProfileEvent(playerProfile).callEvent(); + profile = playerProfile.getGameProfile(); + if (profile.isComplete() && profile.getProperties().containsKey("textures")) { + return profile; + } + GameProfile gameProfile = super.fillProfileProperties(profile, requireSecure); + new FillProfileEvent(CraftPlayerProfile.asBukkitMirror(gameProfile)).callEvent(); + return gameProfile; + } + + @Override + protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) { + return super.fillGameProfile(profile, requireSecure); + } +} diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java new file mode 100644 index 00000000..3aceb0ea --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java @@ -0,0 +1,11 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Agent; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; + +public class PaperUserAuthentication extends YggdrasilUserAuthentication { + public PaperUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) { + super(authenticationService, agent); + } +} diff --git a/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java new file mode 100644 index 00000000..62f7aa2c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java @@ -0,0 +1,151 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.GameProfile; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; +import java.util.UUID; + +/** + * Represents a players profile for the game, such as UUID, Name, and textures. + */ +public interface PlayerProfile { + + /** + * @return The players name, if set + */ + @Nullable String getName(); + + /** + * Sets this profiles Name + * + * @param name The new Name + * @return The previous Name + */ + String setName(@Nullable String name); + + /** + * @return The players unique identifier, if set + */ + @Nullable UUID getId(); + + /** + * Sets this profiles UUID + * + * @param uuid The new UUID + * @return The previous UUID + */ + UUID setId(@Nullable UUID uuid); + + /** + * @return A Mutable set of this players properties, such as textures. + * Values specified here are subject to implementation details. + */ + @Nonnull Set getProperties(); + + /** + * Check if the Profile has the specified property + * @param property Property name to check + * @return If the property is set + */ + boolean hasProperty(String property); + + /** + * Sets a property. If the property already exists, the previous one will be replaced + * @param property Property to set. + */ + void setProperty(ProfileProperty property); + + /** + * Sets multiple properties. If any of the set properties already exist, it will be replaced + * @param properties The properties to set + */ + void setProperties(Collection properties); + + /** + * Removes a specific property from this profile + * @param property The property to remove + * @return If a property was removed + */ + boolean removeProperty(String property); + + /** + * Removes a specific property from this profile + * @param property The property to remove + * @return If a property was removed + */ + default boolean removeProperty(@Nonnull ProfileProperty property) { + return removeProperty(property.getName()); + } + + /** + * Removes all properties in the collection + * @param properties The properties to remove + * @return If any property was removed + */ + default boolean removeProperties(Collection properties) { + boolean removed = false; + for (ProfileProperty property : properties) { + if (removeProperty(property)) { + removed = true; + } + } + return removed; + } + + /** + * Clears all properties on this profile + */ + void clearProperties(); + + /** + * @return If the profile is now complete (has UUID and Name) + */ + boolean isComplete(); + + /** + * Like {@link #complete(boolean)} but will try only from cache, and not make network calls + * Does not account for textures. + * + * @return If the profile is now complete (has UUID and Name) + */ + boolean completeFromCache(); + + /** + * If this profile is not complete, then make the API call to complete it. + * This is a blocking operation and should be done asynchronously. + * + * This will also complete textures. If you do not want to load textures, use {{@link #complete(boolean)}} + * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) + */ + default boolean complete() { + return complete(true); + } + + /** + * If this profile is not complete, then make the API call to complete it. + * This is a blocking operation and should be done asynchronously. + * + * Optionally will also fill textures. + * @param textures controls if we should fill the profile with texture properties + * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) + */ + boolean complete(boolean textures); + + /** + * Whether or not this Profile has textures associated to it + * @return If has a textures property + */ + default boolean hasTextures() { + return hasProperty("textures"); + } + + /** + * @deprecated Will be removed in 1.13 + * @return the GameProfile for this PlayerProfile + */ + @Deprecated + GameProfile getGameProfile(); +} diff --git a/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java b/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java new file mode 100644 index 00000000..afc8d656 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java @@ -0,0 +1,76 @@ +package com.destroystokyo.paper.profile; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +/** + * Represents a property on a {@link PlayerProfile} + */ +public class ProfileProperty { + private final String name; + private final String value; + private final String signature; + + public ProfileProperty(@Nonnull String name, @Nonnull String value) { + this(name, value, null); + } + + public ProfileProperty(@Nonnull String name, @Nonnull String value, @Nullable String signature) { + this.name = Preconditions.checkNotNull(name, "ProfileProperty name can not be null"); + this.value = Preconditions.checkNotNull(value, "ProfileProperty value can not be null"); + this.signature = signature; + } + + /** + * @return The property name, ie "textures" + */ + @Nonnull + public String getName() { + return name; + } + + /** + * @return The property value, likely to be base64 encoded + */ + @Nonnull + public String getValue() { + return value; + } + + /** + * @return A signature from Mojang for signed properties + */ + @Nullable + public String getSignature() { + return signature; + } + + /** + * @return If this property has a signature or not + */ + public boolean isSigned() { + return this.signature != null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProfileProperty that = (ProfileProperty) o; + return Objects.equals(name, that.name) && + Objects.equals(value, that.value) && + Objects.equals(signature, that.signature); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java new file mode 100644 index 00000000..d60ecbb1 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.utils; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.LongAdder; + +public class CachedSizeConcurrentLinkedQueue extends ConcurrentLinkedQueue { + private final LongAdder cachedSize = new LongAdder(); + + @Override + public boolean add(E e) { + boolean result = super.add(e); + if (result) { + cachedSize.increment(); + } + return result; + } + + @Override + public E poll() { + E result = super.poll(); + if (result != null) { + cachedSize.decrement(); + } + return result; + } + + @Override + public int size() { + return cachedSize.intValue(); + } +} diff --git a/src/main/java/kettlefoundation/kettle/KettleVersionCommand.java b/src/main/java/kettlefoundation/kettle/KettleVersionCommand.java new file mode 100644 index 00000000..d7537eed --- /dev/null +++ b/src/main/java/kettlefoundation/kettle/KettleVersionCommand.java @@ -0,0 +1,22 @@ +package kettlefoundation.kettle; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +/** + * KettleVersionCommand + * + * @author strezz + * @since 16/02/2019 - 04:53 PM + */ +public class KettleVersionCommand extends Command { + + protected KettleVersionCommand(String name) { + super(name); + } + + @Override + public boolean execute(CommandSender commandSender, String s, String[] strings) { + return false; + } +} diff --git a/src/main/java/kettlefoundation/kettle/downloads/DownloadServerFiles.java b/src/main/java/kettlefoundation/kettle/downloads/DownloadServerFiles.java new file mode 100644 index 00000000..eff0eb6e --- /dev/null +++ b/src/main/java/kettlefoundation/kettle/downloads/DownloadServerFiles.java @@ -0,0 +1,93 @@ +package kettlefoundation.kettle.downloads; + +import java.io.*; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * DownloadServerFiles + * + * @author Hexeption admin@hexeption.co.uk + * @since 02/05/2019 - 06:06 PM + */ +public class DownloadServerFiles { + + /** + * Downloads required Minecraft server jar from the Minecraft cdn. + */ + public static void downloadMinecraftServer() { + String fileName = "minecraft_server.1.12.2.jar"; + String downloadLink = "https://launcher.mojang.com/v1/objects/886945bfb2b978778c3a0288fd7fab09d315b25f/server.jar"; + + File minecraftServerJar = new File(fileName); + if (!minecraftServerJar.exists() && !minecraftServerJar.isDirectory()) { + System.out.println("Downloading Minecraft Server Jar ..."); + try { + URL website = new URL(downloadLink); + ReadableByteChannel rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(fileName); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Downloads the required libraries zip from the git repo and extracts the libraries zip into the correct place. + */ + public static void downloadServerLibraries() { + String fileName = "libraries.zip"; + String downloadLink = "https://raw.githubusercontent.com/KettleFoundation/Kettle/master/release/libraries.zip"; + + File minecraftlibraries = new File(fileName); + if (!minecraftlibraries.exists() && !minecraftlibraries.isDirectory()) { + System.out.println("Downloading Server Libraries ..."); + try { + URL website = new URL(downloadLink); + ReadableByteChannel rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(fileName); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + System.out.println("Extracting Zip"); + unzip(minecraftlibraries, "."); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Extract files and folders in a zip file. + * + * @param source Zip file to extract data from. + * @param out Folder location to put the extracted data into. + * @throws IOException if there's an error extracting + */ + private static void unzip(File source, String out) throws IOException { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source))) { + ZipEntry entry = zis.getNextEntry(); + while (entry != null) { + File file = new File(out, entry.getName()); + if (entry.isDirectory()) { + file.mkdirs(); + } else { + File parent = file.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { + byte[] buffer = new byte[Math.toIntExact(entry.getSize())]; + int location; + while ((location = zis.read(buffer)) != -1) { + bos.write(buffer, 0, location); + } + } + } + entry = zis.getNextEntry(); + } + } + } +} diff --git a/src/main/java/org/bukkit/Achievement.java b/src/main/java/org/bukkit/Achievement.java new file mode 100644 index 00000000..2a3d7667 --- /dev/null +++ b/src/main/java/org/bukkit/Achievement.java @@ -0,0 +1,72 @@ +package org.bukkit; + +/** + * Represents an achievement, which may be given to players. + * @deprecated future versions of Minecraft do not have achievements + */ +@Deprecated +public enum Achievement { + OPEN_INVENTORY, + MINE_WOOD (OPEN_INVENTORY), + BUILD_WORKBENCH (MINE_WOOD), + BUILD_PICKAXE (BUILD_WORKBENCH), + BUILD_FURNACE (BUILD_PICKAXE), + ACQUIRE_IRON (BUILD_FURNACE), + BUILD_HOE (BUILD_WORKBENCH), + MAKE_BREAD (BUILD_HOE), + BAKE_CAKE (BUILD_HOE), + BUILD_BETTER_PICKAXE (BUILD_PICKAXE), + COOK_FISH (BUILD_FURNACE), + ON_A_RAIL (ACQUIRE_IRON), + BUILD_SWORD (BUILD_WORKBENCH), + KILL_ENEMY (BUILD_SWORD), + KILL_COW (BUILD_SWORD), + FLY_PIG (KILL_COW), + SNIPE_SKELETON (KILL_ENEMY), + GET_DIAMONDS (ACQUIRE_IRON), + NETHER_PORTAL (GET_DIAMONDS), + GHAST_RETURN (NETHER_PORTAL), + GET_BLAZE_ROD (NETHER_PORTAL), + BREW_POTION (GET_BLAZE_ROD), + END_PORTAL (GET_BLAZE_ROD), + THE_END (END_PORTAL), + ENCHANTMENTS (GET_DIAMONDS), + OVERKILL (ENCHANTMENTS), + BOOKCASE (ENCHANTMENTS), + EXPLORE_ALL_BIOMES (END_PORTAL), + SPAWN_WITHER (THE_END), + KILL_WITHER (SPAWN_WITHER), + FULL_BEACON (KILL_WITHER), + BREED_COW (KILL_COW), + DIAMONDS_TO_YOU (GET_DIAMONDS), + OVERPOWERED (BUILD_BETTER_PICKAXE) + ; + + private final Achievement parent; + + private Achievement() { + parent = null; + } + + private Achievement(Achievement parent) { + this.parent = parent; + } + + /** + * Returns whether or not this achievement has a parent achievement. + * + * @return whether the achievement has a parent achievement + */ + public boolean hasParent() { + return parent != null; + } + + /** + * Returns the parent achievement of this achievement, or null if none. + * + * @return the parent achievement or null + */ + public Achievement getParent() { + return parent; + } +} diff --git a/src/main/java/org/bukkit/Art.java b/src/main/java/org/bukkit/Art.java new file mode 100644 index 00000000..55db17e0 --- /dev/null +++ b/src/main/java/org/bukkit/Art.java @@ -0,0 +1,110 @@ +package org.bukkit; + +import java.util.HashMap; + +import org.apache.commons.lang3.Validate; + +import com.google.common.collect.Maps; + +/** + * Represents the art on a painting + */ +public enum Art { + KEBAB(0, 1, 1), + AZTEC(1, 1, 1), + ALBAN(2, 1, 1), + AZTEC2(3, 1, 1), + BOMB(4, 1, 1), + PLANT(5, 1, 1), + WASTELAND(6, 1, 1), + POOL(7, 2, 1), + COURBET(8, 2, 1), + SEA(9, 2, 1), + SUNSET(10, 2, 1), + CREEBET(11, 2, 1), + WANDERER(12, 1, 2), + GRAHAM(13, 1, 2), + MATCH(14, 2, 2), + BUST(15, 2, 2), + STAGE(16, 2, 2), + VOID(17, 2, 2), + SKULL_AND_ROSES(18, 2, 2), + WITHER(19, 2, 2), + FIGHTERS(20, 4, 2), + POINTER(21, 4, 4), + PIGSCENE(22, 4, 4), + BURNINGSKULL(23, 4, 4), + SKELETON(24, 4, 3), + DONKEYKONG(25, 4, 3); + + private int id, width, height; + private static final HashMap BY_NAME = Maps.newHashMap(); + private static final HashMap BY_ID = Maps.newHashMap(); + + private Art(int id, int width, int height) { + this.id = id; + this.width = width; + this.height = height; + } + + /** + * Gets the width of the painting, in blocks + * + * @return The width of the painting, in blocks + */ + public int getBlockWidth() { + return width; + } + + /** + * Gets the height of the painting, in blocks + * + * @return The height of the painting, in blocks + */ + public int getBlockHeight() { + return height; + } + + /** + * Get the ID of this painting. + * + * @return The ID of this painting + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Get a painting by its numeric ID + * + * @param id The ID + * @return The painting + * @deprecated Magic value + */ + @Deprecated + public static Art getById(int id) { + return BY_ID.get(id); + } + + /** + * Get a painting by its unique name + *

+ * This ignores underscores and capitalization + * + * @param name The name + * @return The painting + */ + public static Art getByName(String name) { + Validate.notNull(name, "Name cannot be null"); + return BY_NAME.get(name.toLowerCase(java.util.Locale.ENGLISH).replaceAll("_", "")); + } + + static { + for (Art art : values()) { + BY_ID.put(art.id, art); + BY_NAME.put(art.toString().toLowerCase(java.util.Locale.ENGLISH).replaceAll("_", ""), art); + } + } +} diff --git a/src/main/java/org/bukkit/BanEntry.java b/src/main/java/org/bukkit/BanEntry.java new file mode 100644 index 00000000..b2437c6d --- /dev/null +++ b/src/main/java/org/bukkit/BanEntry.java @@ -0,0 +1,128 @@ +package org.bukkit; + +import java.util.Date; + +/** + * A single entry from a ban list. This may represent either a player ban or + * an IP ban. + *

+ * Ban entries include the following properties: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Property information
PropertyDescription
Target Name / IP AddressThe target name or IP address
Creation DateThe creation date of the ban
SourceThe source of the ban, such as a player, console, plugin, etc
Expiration DateThe expiration date of the ban
ReasonThe reason for the ban
+ *

+ * Unsaved information is not automatically written to the implementation's + * ban list, instead, the {@link #save()} method must be called to write the + * changes to the ban list. If this ban entry has expired (such as from an + * unban) and is no longer found in the list, the {@link #save()} call will + * re-add it to the list, therefore banning the victim specified. + *

+ * Likewise, changes to the associated {@link BanList} or other entries may or + * may not be reflected in this entry. + */ +public interface BanEntry { + + /** + * Gets the target involved. This may be in the form of an IP or a player + * name. + * + * @return the target name or IP address + */ + public String getTarget(); + + /** + * Gets the date this ban entry was created. + * + * @return the creation date + */ + public Date getCreated(); + + /** + * Sets the date this ban entry was created. + * + * @param created the new created date, cannot be null + * @see #save() saving changes + */ + public void setCreated(Date created); + + /** + * Gets the source of this ban. + *

+ * Note: A source is considered any String, although this is generally a + * player name. + * + * @return the source of the ban + */ + public String getSource(); + + /** + * Sets the source of this ban. + *

+ * Note: A source is considered any String, although this is generally a + * player name. + * + * @param source the new source where null values become empty strings + * @see #save() saving changes + */ + public void setSource(String source); + + /** + * Gets the date this ban expires on, or null for no defined end date. + * + * @return the expiration date + */ + public Date getExpiration(); + + /** + * Sets the date this ban expires on. Null values are considered + * "infinite" bans. + * + * @param expiration the new expiration date, or null to indicate an + * eternity + * @see #save() saving changes + */ + public void setExpiration(Date expiration); + + /** + * Gets the reason for this ban. + * + * @return the ban reason, or null if not set + */ + public String getReason(); + + /** + * Sets the reason for this ban. Reasons must not be null. + * + * @param reason the new reason, null values assume the implementation + * default + * @see #save() saving changes + */ + public void setReason(String reason); + + /** + * Saves the ban entry, overwriting any previous data in the ban list. + *

+ * Saving the ban entry of an unbanned player will cause the player to be + * banned once again. + */ + public void save(); +} diff --git a/src/main/java/org/bukkit/BanList.java b/src/main/java/org/bukkit/BanList.java new file mode 100644 index 00000000..c21b858e --- /dev/null +++ b/src/main/java/org/bukkit/BanList.java @@ -0,0 +1,72 @@ +package org.bukkit; + +import java.util.Date; +import java.util.Set; + +/** + * A ban list, containing bans of some {@link Type}. + */ +public interface BanList { + + /** + * Represents a ban-type that a {@link BanList} may track. + */ + public enum Type { + /** + * Banned player names + */ + NAME, + /** + * Banned player IP addresses + */ + IP, + ; + } + + /** + * Gets a {@link BanEntry} by target. + * + * @param target entry parameter to search for + * @return the corresponding entry, or null if none found + */ + public BanEntry getBanEntry(String target); + + /** + * Adds a ban to the this list. If a previous ban exists, this will + * update the previous entry. + * + * @param target the target of the ban + * @param reason reason for the ban, null indicates implementation default + * @param expires date for the ban's expiration (unban), or null to imply + * forever + * @param source source of the ban, null indicates implementation default + * @return the entry for the newly created ban, or the entry for the + * (updated) previous ban + */ + public BanEntry addBan(String target, String reason, Date expires, String source); + + /** + * Gets a set containing every {@link BanEntry} in this list. + * + * @return an immutable set containing every entry tracked by this list + */ + public Set getBanEntries(); + + /** + * Gets if a {@link BanEntry} exists for the target, indicating an active + * ban status. + * + * @param target the target to find + * @return true if a {@link BanEntry} exists for the name, indicating an + * active ban status, false otherwise + */ + public boolean isBanned(String target); + + /** + * Removes the specified target from this list, therefore indicating a + * "not banned" status. + * + * @param target the target to remove from this list + */ + public void pardon(String target); +} diff --git a/src/main/java/org/bukkit/BlockChangeDelegate.java b/src/main/java/org/bukkit/BlockChangeDelegate.java new file mode 100644 index 00000000..3f1afd9c --- /dev/null +++ b/src/main/java/org/bukkit/BlockChangeDelegate.java @@ -0,0 +1,106 @@ +package org.bukkit; + +/** + * A delegate for handling block changes. This serves as a direct interface + * between generation algorithms in the server implementation and utilizing + * code. + * @deprecated rarely used API that was largely for implementation purposes + */ +@Deprecated +public interface BlockChangeDelegate { + + /** + * Set a block type at the specified coordinates without doing all world + * updates and notifications. + *

+ * It is safe to have this call World.setTypeId, but it may be slower than + * World.setRawTypeId. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param typeId New block ID + * @return true if the block was set successfully + * @deprecated Magic value + */ + @Deprecated + public boolean setRawTypeId(int x, int y, int z, int typeId); + + /** + * Set a block type and data at the specified coordinates without doing + * all world updates and notifications. + *

+ * It is safe to have this call World.setTypeId, but it may be slower than + * World.setRawTypeId. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param typeId New block ID + * @param data Block data + * @return true if the block was set successfully + * @deprecated Magic value + */ + @Deprecated + public boolean setRawTypeIdAndData(int x, int y, int z, int typeId, int data); + + /** + * Set a block type at the specified coordinates. + *

+ * This method cannot call World.setRawTypeId, a full update is needed. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param typeId New block ID + * @return true if the block was set successfully + * @deprecated Magic value + */ + @Deprecated + public boolean setTypeId(int x, int y, int z, int typeId); + + /** + * Set a block type and data at the specified coordinates. + *

+ * This method cannot call World.setRawTypeId, a full update is needed. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param typeId New block ID + * @param data Block data + * @return true if the block was set successfully + * @deprecated Magic value + */ + @Deprecated + public boolean setTypeIdAndData(int x, int y, int z, int typeId, int data); + + /** + * Get the block type at the location. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return The block ID + * @deprecated Magic value + */ + @Deprecated + public int getTypeId(int x, int y, int z); + + /** + * Gets the height of the world. + * + * @return Height of the world + */ + public int getHeight(); + + /** + * Checks if the specified block is empty (air) or not. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return True if the block is considered empty. + */ + public boolean isEmpty(int x, int y, int z); +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java new file mode 100644 index 00000000..b978e422 --- /dev/null +++ b/src/main/java/org/bukkit/Bukkit.java @@ -0,0 +1,1219 @@ +package org.bukkit; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bukkit.Warning.WarningState; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Recipe; +import org.bukkit.map.MapView; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.util.CachedServerIcon; + +import com.google.common.collect.ImmutableList; +import org.bukkit.advancement.Advancement; +import org.bukkit.generator.ChunkGenerator; + +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Represents the Bukkit core, for version and Server singleton handling + */ +public final class Bukkit { + private static Server server; + + /** + * Static class cannot be initialized. + */ + private Bukkit() {} + + /** + * Gets the current {@link Server} singleton + * + * @return Server instance being ran + */ + public static Server getServer() { + return server; + } + + /** + * Attempts to set the {@link Server} singleton. + *

+ * This cannot be done if the Server is already set. + * + * @param server Server instance + */ + public static void setServer(Server server) { + if (Bukkit.server != null) { + throw new UnsupportedOperationException("Cannot redefine singleton Server"); + } + + Bukkit.server = server; + server.getLogger().info("This server is running " + getName() + " version " + getVersion() + " (Implementing API version " + getBukkitVersion() + ")"); + } + + /** + * Gets the name of this server implementation. + * + * @return name of this server implementation + */ + public static String getName() { + return server.getName(); + } + + /** + * Gets the version string of this server implementation. + * + * @return version of this server implementation + */ + public static String getVersion() { + return server.getVersion(); + } + + /** + * Gets the Bukkit version that this server is running. + * + * @return version of Bukkit + */ + public static String getBukkitVersion() { + return server.getBukkitVersion(); + } + + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused + * object, making some operations like {@link Collection#size()} + * zero-allocation. + *

+ * The collection is a view backed by the internal representation, such + * that, changes to the internal state of the server will be reflected + * immediately. However, the reuse of the returned collection (identity) + * is not strictly guaranteed for future or all implementations. Casting + * the collection, or relying on interface implementations (like {@link + * Serializable} or {@link List}), is deprecated. + *

+ * Iteration behavior is undefined outside of self-contained main-thread + * uses. Normal and immediate iterator use without consequences that + * affect the collection are fully supported. The effects following + * (non-exhaustive) {@link Entity#teleport(Location) teleportation}, + * {@link Player#setHealth(double) death}, and {@link Player#kickPlayer( + * String) kicking} are undefined. Any use of this collection from + * asynchronous threads is unsafe. + *

+ * For safe consequential iteration or mimicking the old array behavior, + * using {@link Collection#toArray(Object[])} is recommended. For making + * snapshots, {@link ImmutableList#copyOf(Collection)} is recommended. + * + * @return a view of currently online players. + */ + public static Collection getOnlinePlayers() { + return server.getOnlinePlayers(); + } + + public static Player[] _INVALID_getOnlinePlayers() { + return server._INVALID_getOnlinePlayers(); + } + + /** + * Get the maximum amount of players which can login to this server. + * + * @return the amount of players this server allows + */ + public static int getMaxPlayers() { + return server.getMaxPlayers(); + } + + /** + * Get the game port that the server runs on. + * + * @return the port number of this server + */ + public static int getPort() { + return server.getPort(); + } + + /** + * Get the view distance from this server. + * + * @return the view distance from this server. + */ + public static int getViewDistance() { + return server.getViewDistance(); + } + + /** + * Get the IP that this server is bound to, or empty string if not + * specified. + * + * @return the IP string that this server is bound to, otherwise empty + * string + */ + public static String getIp() { + return server.getIp(); + } + + /** + * Get the name of this server. + * + * @return the name of this server + */ + public static String getServerName() { + return server.getServerName(); + } + + /** + * Get an ID of this server. The ID is a simple generally alphanumeric ID + * that can be used for uniquely identifying this server. + * + * @return the ID of this server + */ + public static String getServerId() { + return server.getServerId(); + } + + /** + * Get world type (level-type setting) for default world. + * + * @return the value of level-type (e.g. DEFAULT, FLAT, DEFAULT_1_1) + */ + public static String getWorldType() { + return server.getWorldType(); + } + + /** + * Get generate-structures setting. + * + * @return true if structure generation is enabled, false otherwise + */ + public static boolean getGenerateStructures() { + return server.getGenerateStructures(); + } + + /** + * Gets whether this server allows the End or not. + * + * @return whether this server allows the End or not + */ + public static boolean getAllowEnd() { + return server.getAllowEnd(); + } + + /** + * Gets whether this server allows the Nether or not. + * + * @return whether this server allows the Nether or not + */ + public static boolean getAllowNether() { + return server.getAllowNether(); + } + + /** + * Gets whether this server has a whitelist or not. + * + * @return whether this server has a whitelist or not + */ + public static boolean hasWhitelist() { + return server.hasWhitelist(); + } + + /** + * Sets if the server is whitelisted. + * + * @param value true for whitelist on, false for off + */ + public static void setWhitelist(boolean value) { + server.setWhitelist(value); + } + + /** + * Gets a list of whitelisted players. + * + * @return a set containing all whitelisted players + */ + public static Set getWhitelistedPlayers() { + return server.getWhitelistedPlayers(); + } + + /** + * Reloads the whitelist from disk. + */ + public static void reloadWhitelist() { + server.reloadWhitelist(); + } + + /** + * Broadcast a message to all players. + *

+ * This is the same as calling {@link #broadcast(String, + * String)} to {@link Server#BROADCAST_CHANNEL_USERS} + * + * @param message the message + * @return the number of players + */ + public static int broadcastMessage(String message) { + return server.broadcastMessage(message); + } + + /** + * Gets the name of the update folder. The update folder is used to safely + * update plugins at the right moment on a plugin load. + *

+ * The update folder name is relative to the plugins folder. + * + * @return the name of the update folder + */ + public static String getUpdateFolder() { + return server.getUpdateFolder(); + } + + /** + * Gets the update folder. The update folder is used to safely update + * plugins at the right moment on a plugin load. + * + * @return the update folder + */ + public static File getUpdateFolderFile() { + return server.getUpdateFolderFile(); + } + + /** + * Gets the value of the connection throttle setting. + * + * @return the value of the connection throttle setting + */ + public static long getConnectionThrottle() { + return server.getConnectionThrottle(); + } + + /** + * Gets default ticks per animal spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, animal spawning will be disabled. We + * recommend using spawn-animals to control this instead. + *

+ * Minecraft default: 400. + * + * @return the default ticks per animal spawns value + */ + public static int getTicksPerAnimalSpawns() { + return server.getTicksPerAnimalSpawns(); + } + + /** + * Gets the default ticks per monster spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, monsters spawning will be disabled. We + * recommend using spawn-monsters to control this instead. + *

+ * Minecraft default: 1. + * + * @return the default ticks per monsters spawn value + */ + public static int getTicksPerMonsterSpawns() { + return server.getTicksPerMonsterSpawns(); + } + + /** + * Gets a player object by the given username. + *

+ * This method may not return objects for offline players. + * + * @param name the name to look up + * @return a player if one was found, null otherwise + */ + public static Player getPlayer(String name) { + return server.getPlayer(name); + } + + /** + * Gets the player with the exact given name, case insensitive. + * + * @param name Exact name of the player to retrieve + * @return a player object if one was found, null otherwise + */ + public static Player getPlayerExact(String name) { + return server.getPlayerExact(name); + } + + /** + * Attempts to match any players with the given name, and returns a list + * of all possibly matches. + *

+ * This list is not sorted in any particular order. If an exact match is + * found, the returned list will only contain a single result. + * + * @param name the (partial) name to match + * @return list of all possible players + */ + public static List matchPlayer(String name) { + return server.matchPlayer(name); + } + + /** + * Gets the player with the given UUID. + * + * @param id UUID of the player to retrieve + * @return a player object if one was found, null otherwise + */ + public static Player getPlayer(UUID id) { + return server.getPlayer(id); + } + + /** + * Gets the plugin manager for interfacing with plugins. + * + * @return a plugin manager for this Server instance + */ + public static PluginManager getPluginManager() { + return server.getPluginManager(); + } + + /** + * Gets the scheduler for managing scheduled events. + * + * @return a scheduling service for this server + */ + public static BukkitScheduler getScheduler() { + return server.getScheduler(); + } + + /** + * Gets a services manager. + * + * @return s services manager + */ + public static ServicesManager getServicesManager() { + return server.getServicesManager(); + } + + /** + * Gets a list of all worlds on this server. + * + * @return a list of worlds + */ + public static List getWorlds() { + return server.getWorlds(); + } + + /** + * Creates or loads a world with the given name using the specified + * options. + *

+ * If the world is already loaded, it will just return the equivalent of + * getWorld(creator.name()). + * + * @param creator the options to use when creating the world + * @return newly created or loaded world + */ + public static World createWorld(WorldCreator creator) { + return server.createWorld(creator); + } + + /** + * Unloads a world with the given name. + * + * @param name Name of the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public static boolean unloadWorld(String name, boolean save) { + return server.unloadWorld(name, save); + } + + /** + * Unloads the given world. + * + * @param world the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public static boolean unloadWorld(World world, boolean save) { + return server.unloadWorld(world, save); + } + + /** + * Gets the world with the given name. + * + * @param name the name of the world to retrieve + * @return a world with the given name, or null if none exists + */ + public static World getWorld(String name) { + return server.getWorld(name); + } + + /** + * Gets the world from the given Unique ID. + * + * @param uid a unique-id of the world to retrieve + * @return a world with the given Unique ID, or null if none exists + */ + public static World getWorld(UUID uid) { + return server.getWorld(uid); + } + + /** + * Gets the map from the given item ID. + * + * @param id the id of the map to get + * @return a map view if it exists, or null otherwise + * @deprecated Magic value + */ + + public static MapView getMap(short id) { + return server.getMap(id); + } + + /** + * Create a new map with an automatically assigned ID. + * + * @param world the world the map will belong to + * @return a newly created map view + */ + public static MapView createMap(World world) { + return server.createMap(world); + } + + /** + * Reloads the server, refreshing settings and plugin information. + */ + public static void reload() { + server.reload(); + } + + /** + * Reload only the Minecraft data for the server. This includes custom + * advancements and loot tables. + */ + public static void reloadData() { + server.reloadData(); + } + + /** + * Returns the primary logger associated with this server instance. + * + * @return Logger associated with this server + */ + public static Logger getLogger() { + return server.getLogger(); + } + + /** + * Gets a {@link PluginCommand} with the given name or alias. + * + * @param name the name of the command to retrieve + * @return a plugin command if found, null otherwise + */ + public static PluginCommand getPluginCommand(String name) { + return server.getPluginCommand(name); + } + + /** + * Writes loaded players to disk. + */ + public static void savePlayers() { + server.savePlayers(); + } + + /** + * Dispatches a command on this server, and executes it if found. + * + * @param sender the apparent sender of the command + * @param commandLine the command + arguments. Example: test abc + * 123 + * @return returns false if no target is found + * @throws CommandException thrown when the executor for the given command + * fails with an unhandled exception + */ + public static boolean dispatchCommand(CommandSender sender, String commandLine) throws CommandException { + return server.dispatchCommand(sender, commandLine); + } + + /** + * Adds a recipe to the crafting manager. + * + * @param recipe the recipe to add + * @return true if the recipe was added, false if it wasn't for some + * reason + */ + public static boolean addRecipe(Recipe recipe) { + return server.addRecipe(recipe); + } + + /** + * Get a list of all recipes for a given item. The stack size is ignored + * in comparisons. If the durability is -1, it will match any data value. + * + * @param result the item to match against recipe results + * @return a list of recipes with the given result + */ + public static List getRecipesFor(ItemStack result) { + return server.getRecipesFor(result); + } + + /** + * Get an iterator through the list of crafting recipes. + * + * @return an iterator + */ + public static Iterator recipeIterator() { + return server.recipeIterator(); + } + + /** + * Clears the list of crafting recipes. + */ + public static void clearRecipes() { + server.clearRecipes(); + } + + /** + * Resets the list of crafting recipes to the default. + */ + public static void resetRecipes() { + server.resetRecipes(); + } + + /** + * Gets a list of command aliases defined in the server properties. + * + * @return a map of aliases to command names + */ + public static Map getCommandAliases() { + return server.getCommandAliases(); + } + + /** + * Gets the radius, in blocks, around each worlds spawn point to protect. + * + * @return spawn radius, or 0 if none + */ + public static int getSpawnRadius() { + return server.getSpawnRadius(); + } + + /** + * Sets the radius, in blocks, around each worlds spawn point to protect. + * + * @param value new spawn radius, or 0 if none + */ + public static void setSpawnRadius(int value) { + server.setSpawnRadius(value); + } + + /** + * Gets whether the Server is in online mode or not. + * + * @return true if the server authenticates clients, false otherwise + */ + public static boolean getOnlineMode() { + return server.getOnlineMode(); + } + + /** + * Gets whether this server allows flying or not. + * + * @return true if the server allows flight, false otherwise + */ + public static boolean getAllowFlight() { + return server.getAllowFlight(); + } + + /** + * Gets whether the server is in hardcore mode or not. + * + * @return true if the server mode is hardcore, false otherwise + */ + public static boolean isHardcore() { + return server.isHardcore(); + } + + /** + * Shutdowns the server, stopping everything. + */ + public static void shutdown() { + server.shutdown(); + } + + /** + * Broadcasts the specified message to every user with the given + * permission name. + * + * @param message message to broadcast + * @param permission the required permission {@link Permissible + * permissibles} must have to receive the broadcast + * @return number of message recipients + */ + public static int broadcast(String message, String permission) { + return server.broadcast(message, permission); + } + + /** + * Gets the player by the given name, regardless if they are offline or + * online. + *

+ * This method may involve a blocking web request to get the UUID for the + * given name. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @deprecated Persistent storage of users should be by UUID as names are no longer + * unique past a single session. + * @param name the name the player to retrieve + * @return an offline player + * @see #getOfflinePlayer(UUID) + */ + @Deprecated + public static OfflinePlayer getOfflinePlayer(String name) { + return server.getOfflinePlayer(name); + } + + /** + * Gets the player by the given UUID, regardless if they are offline or + * online. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @param id the UUID of the player to retrieve + * @return an offline player + */ + public static OfflinePlayer getOfflinePlayer(UUID id) { + return server.getOfflinePlayer(id); + } + + /** + * Gets a set containing all current IPs that are banned. + * + * @return a set containing banned IP addresses + */ + public static Set getIPBans() { + return server.getIPBans(); + } + + /** + * Bans the specified address from the server. + * + * @param address the IP address to ban + */ + public static void banIP(String address) { + server.banIP(address); + } + + /** + * Unbans the specified address from the server. + * + * @param address the IP address to unban + */ + public static void unbanIP(String address) { + server.unbanIP(address); + } + + /** + * Gets a set containing all banned players. + * + * @return a set containing banned players + */ + public static Set getBannedPlayers() { + return server.getBannedPlayers(); + } + + /** + * Gets a ban list for the supplied type. + *

+ * Bans by name are no longer supported and this method will return + * null when trying to request them. The replacement is bans by UUID. + * + * @param type the type of list to fetch, cannot be null + * @return a ban list of the specified type + */ + public static BanList getBanList(BanList.Type type) { + return server.getBanList(type); + } + + /** + * Gets a set containing all player operators. + * + * @return a set containing player operators + */ + public static Set getOperators() { + return server.getOperators(); + } + + /** + * Gets the default {@link GameMode} for new players. + * + * @return the default game mode + */ + public static GameMode getDefaultGameMode() { + return server.getDefaultGameMode(); + } + + /** + * Sets the default {@link GameMode} for new players. + * + * @param mode the new game mode + */ + public static void setDefaultGameMode(GameMode mode) { + server.setDefaultGameMode(mode); + } + + /** + * Gets a {@link ConsoleCommandSender} that may be used as an input source + * for this server. + * + * @return a console command sender + */ + public static ConsoleCommandSender getConsoleSender() { + return server.getConsoleSender(); + } + + /** + * Gets the folder that contains all of the various {@link World}s. + * + * @return folder that contains all worlds + */ + public static File getWorldContainer() { + return server.getWorldContainer(); + } + + /** + * Gets every player that has ever played on this server. + * + * @return an array containing all previous players + */ + public static OfflinePlayer[] getOfflinePlayers() { + return server.getOfflinePlayers(); + } + + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return messenger responsible for this server + */ + public static Messenger getMessenger() { + return server.getMessenger(); + } + + /** + * Gets the {@link HelpMap} providing help topics for this server. + * + * @return a help map for this server + */ + public static HelpMap getHelpMap() { + return server.getHelpMap(); + } + + /** + * Creates an empty inventory of the specified type. If the type is {@link + * InventoryType#CHEST}, the new inventory has a size of 27; otherwise the + * new inventory has the normal size for its type. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param type the type of inventory to create + * @return a new inventory + */ + public static Inventory createInventory(InventoryHolder owner, InventoryType type) { + return server.createInventory(owner, type); + } + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + * + * @param owner The holder of the inventory; can be null if there's no holder. + * @param type The type of inventory to create. + * @param title The title of the inventory, to be displayed when it is viewed. + * @return The new inventory. + */ + public static Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + return server.createInventory(owner, type, title); + } + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + public static Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException { + return server.createInventory(owner, size); + } + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size and title. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @param title the title of the inventory, displayed when inventory is + * viewed + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + public static Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException { + return server.createInventory(owner, size, title); + } + + /** + * Creates an empty merchant. + * + * @param title the title of the corresponding merchant inventory, displayed + * when the merchant inventory is viewed + * @return a new merchant + */ + public static Merchant createMerchant(String title) { + return server.createMerchant(title); + } + + /** + * Gets user-specified limit for number of monsters that can spawn in a + * chunk. + * + * @return the monster spawn limit + */ + public static int getMonsterSpawnLimit() { + return server.getMonsterSpawnLimit(); + } + + /** + * Gets user-specified limit for number of animals that can spawn in a + * chunk. + * + * @return the animal spawn limit + */ + public static int getAnimalSpawnLimit() { + return server.getAnimalSpawnLimit(); + } + + /** + * Gets user-specified limit for number of water animals that can spawn in + * a chunk. + * + * @return the water animal spawn limit + */ + public static int getWaterAnimalSpawnLimit() { + return server.getWaterAnimalSpawnLimit(); + } + + /** + * Gets user-specified limit for number of ambient mobs that can spawn in + * a chunk. + * + * @return the ambient spawn limit + */ + public static int getAmbientSpawnLimit() { + return server.getAmbientSpawnLimit(); + } + + /** + * Checks the current thread against the expected primary thread for the + * server. + *

+ * Note: this method should not be used to indicate the current + * synchronized state of the runtime. A current thread matching the main + * thread indicates that it is synchronized, but a mismatch does not + * preclude the same assumption. + * + * @return true if the current thread matches the expected primary thread, + * false otherwise + */ + public static boolean isPrimaryThread() { + return server.isPrimaryThread(); + } + + /** + * Gets the message that is displayed on the server list. + * + * @return the servers MOTD + */ + public static String getMotd() { + return server.getMotd(); + } + + /** + * Gets the default message that is displayed when the server is stopped. + * + * @return the shutdown message + */ + public static String getShutdownMessage() { + return server.getShutdownMessage(); + } + + /** + * Gets the current warning state for the server. + * + * @return the configured warning state + */ + public static WarningState getWarningState() { + return server.getWarningState(); + } + + /** + * Gets the instance of the item factory (for {@link ItemMeta}). + * + * @return the item factory + * @see ItemFactory + */ + public static ItemFactory getItemFactory() { + return server.getItemFactory(); + } + + /** + * Gets the instance of the scoreboard manager. + *

+ * This will only exist after the first world has loaded. + * + * @return the scoreboard manager or null if no worlds are loaded. + */ + public static ScoreboardManager getScoreboardManager() { + return server.getScoreboardManager(); + } + + /** + * Gets an instance of the server's default server-icon. + * + * @return the default server-icon; null values may be used by the + * implementation to indicate no defined icon, but this behavior is + * not guaranteed + */ + public static CachedServerIcon getServerIcon() { + return server.getServerIcon(); + } + + /** + * Loads an image from a file, and returns a cached image for the specific + * server-icon. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param file the file to load the from + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server server-icon + * specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + public static CachedServerIcon loadServerIcon(File file) throws IllegalArgumentException, Exception { + return server.loadServerIcon(file); + } + + /** + * Creates a cached server-icon for the specific image. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param image the image to use + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server + * server-icon specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + public static CachedServerIcon loadServerIcon(BufferedImage image) throws IllegalArgumentException, Exception { + return server.loadServerIcon(image); + } + + /** + * Set the idle kick timeout. Any players idle for the specified amount of + * time will be automatically kicked. + *

+ * A value of 0 will disable the idle kick timeout. + * + * @param threshold the idle timeout in minutes + */ + public static void setIdleTimeout(int threshold) { + server.setIdleTimeout(threshold); + } + + /** + * Gets the idle kick timeout. + * + * @return the idle timeout in minutes + */ + public static int getIdleTimeout() { + return server.getIdleTimeout(); + } + + /** + * Create a ChunkData for use in a generator. + * + * See {@link ChunkGenerator#generateChunkData(World, java.util.Random, int, int, ChunkGenerator.BiomeGrid)} + * + * @param world the world to create the ChunkData for + * @return a new ChunkData for the world + * + */ + public static ChunkGenerator.ChunkData createChunkData(World world) { + return server.createChunkData(world); + } + + /** + * Creates a boss bar instance to display to players. The progress + * defaults to 1.0 + * + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + public static BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { + return server.createBossBar(title, color, style, flags); + } + + /** + * Gets an entity on the server by its UUID + * + * @param uuid the UUID of the entity + * @return the entity with the given UUID, or null if it isn't found + */ + public static Entity getEntity(UUID uuid) { + return server.getEntity(uuid); + } + + // Paper start + /** + * Gets the current server TPS + * @return current server TPS (1m, 5m, 15m in Paper-Server) + */ + public static double[] getTPS() { + return server.getTPS(); + } + // Paper end + + /** + * Get the advancement specified by this key. + * + * @param key unique advancement key + * @return advancement or null if not exists + */ + public static Advancement getAdvancement(NamespacedKey key) { + return server.getAdvancement(key); + } + + /** + * Get an iterator through all advancements. Advancements cannot be removed + * from this iterator, + * + * @return an advancement iterator + */ + public static Iterator advancementIterator() { + return server.advancementIterator(); + } + + /** + * @see UnsafeValues + * @return the unsafe values instance + */ + + public static UnsafeValues getUnsafe() { + return server.getUnsafe(); + } + + public static Server.Spigot spigot() + { + return server.spigot(); + } + + /** + * Checks if player names should be suggested when a command returns {@code null} as + * their tab completion result. + * + * @return true if player names should be suggested + */ + public static boolean suggestPlayerNamesWhenNullTabCompletions() { + return server.suggestPlayerNamesWhenNullTabCompletions(); + } + + /** + * Creates a PlayerProfile for the specified uuid, with name as null + * @param uuid UUID to create profile for + * @return A PlayerProfile object + */ + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return server.createProfile(uuid); + } + + /** + * Creates a PlayerProfile for the specified name, with UUID as null + * @param name Name to create profile for + * @return A PlayerProfile object + */ + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { + return server.createProfile(name); + } + + /** + * Creates a PlayerProfile for the specified name/uuid + * + * Both UUID and Name can not be null at same time. One must be supplied. + * + * @param uuid UUID to create profile for + * @param name Name to create profile for + * @return A PlayerProfile object + */ + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { + return server.createProfile(uuid, name); + } + // Paper end +} diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java new file mode 100644 index 00000000..c4aa0577 --- /dev/null +++ b/src/main/java/org/bukkit/ChatColor.java @@ -0,0 +1,371 @@ +package org.bukkit; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.Validate; + +import com.google.common.collect.Maps; + +/** + * All supported color values for chat + */ +public enum ChatColor{ + /** + * Represents black + */ + BLACK('0', 0x00) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLACK; + } + }, + /** + * Represents dark blue + */ + DARK_BLUE('1', 0x1){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_BLUE; + } + }, + /** + * Represents dark green + */ + DARK_GREEN('2', 0x2){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GREEN; + } + }, + /** + * Represents dark blue (aqua) + */ + DARK_AQUA('3', 0x3){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_AQUA; + } + }, + /** + * Represents dark red + */ + DARK_RED('4', 0x4){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_RED; + } + }, + /** + * Represents dark purple + */ + DARK_PURPLE('5', 0x5){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_PURPLE; + } + }, + /** + * Represents gold + */ + GOLD('6', 0x6){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GOLD; + } + }, + /** + * Represents gray + */ + GRAY('7', 0x7){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GRAY; + } + }, + /** + * Represents dark gray + */ + DARK_GRAY('8', 0x8){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GRAY; + } + }, + /** + * Represents blue + */ + BLUE('9', 0x9){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLUE; + } + }, + /** + * Represents green + */ + GREEN('a', 0xA){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GREEN; + } + }, + /** + * Represents aqua + */ + AQUA('b', 0xB){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.AQUA; + } + }, + /** + * Represents red + */ + RED('c', 0xC){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RED; + } + }, + /** + * Represents light purple + */ + LIGHT_PURPLE('d', 0xD){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.LIGHT_PURPLE; + } + }, + /** + * Represents yellow + */ + YELLOW('e', 0xE){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.YELLOW; + } + }, + /** + * Represents white + */ + WHITE('f', 0xF){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.WHITE; + } + }, + /** + * Represents magical characters that change around randomly + */ + MAGIC('k', 0x10, true){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.MAGIC; + } + }, + /** + * Makes the text bold. + */ + BOLD('l', 0x11, true){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BOLD; + } + }, + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH('m', 0x12, true){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.STRIKETHROUGH; + } + }, + /** + * Makes the text appear underlined. + */ + UNDERLINE('n', 0x13, true){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.UNDERLINE; + } + }, + /** + * Makes the text italic. + */ + ITALIC('o', 0x14, true){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.ITALIC; + } + }, + /** + * Resets all previous chat colors or formats. + */ + RESET('r', 0x15){ + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + } + }; + + /** + * The special character which prefixes all chat colour codes. Use this if + * you need to dynamically convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]"); + + private final int intCode; + private final char code; + private final boolean isFormat; + private final String toString; + private final static Map BY_ID = Maps.newHashMap(); + private final static Map BY_CHAR = Maps.newHashMap(); + + private ChatColor(char code, int intCode) { + this(code, intCode, false); + } + + private ChatColor(char code, int intCode, boolean isFormat) { + this.code = code; + this.intCode = intCode; + this.isFormat = isFormat; + this.toString = new String(new char[] {COLOR_CHAR, code}); + } + + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + }; + + /** + * Gets the char value associated with this color + * + * @return A char value of this color code + */ + public char getChar() { + return code; + } + + @Override + public String toString() { + return toString; + } + + /** + * Checks if this code is a format code as opposed to a color code. + * + * @return whether this ChatColor is a format code + */ + public boolean isFormat() { + return isFormat; + } + + /** + * Checks if this code is a color code as opposed to a format code. + * + * @return whether this ChatColor is a color code + */ + public boolean isColor() { + return !isFormat && this != RESET; + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link ChatColor} with the given code, + * or null if it doesn't exist + */ + public static ChatColor getByChar(char code) { + return BY_CHAR.get(code); + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link ChatColor} with the given code, + * or null if it doesn't exist + */ + public static ChatColor getByChar(String code) { + Validate.notNull(code, "Code cannot be null"); + Validate.isTrue(code.length() > 0, "Code must have at least one char"); + + return BY_CHAR.get(code.charAt(0)); + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) { + if (input == null) { + return null; + } + + return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); + } + + /** + * Translates a string using an alternate color code character into a + * string that uses the internal ChatColor.COLOR_CODE color code + * character. The alternate color code character will only be replaced if + * it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r. + * + * @param altColorChar The alternate color code character to replace. Ex: {@literal &} + * @param textToTranslate Text containing the alternate color code character. + * @return Text containing the ChatColor.COLOR_CODE color code character. + */ + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) { + b[i] = ChatColor.COLOR_CHAR; + b[i+1] = Character.toLowerCase(b[i+1]); + } + } + return new String(b); + } + + /** + * Gets the ChatColors used at the end of the given input string. + * + * @param input Input string to retrieve the colors from. + * @return Any remaining ChatColors to pass onto the next line. + */ + public static String getLastColors(String input) { + String result = ""; + int length = input.length(); + + // Search backwards from the end as it is faster + for (int index = length - 1; index > -1; index--) { + char section = input.charAt(index); + if (section == COLOR_CHAR && index < length - 1) { + char c = input.charAt(index + 1); + ChatColor color = getByChar(c); + + if (color != null) { + result = color.toString() + result; + + // Once we find a color or reset we can stop searching + if (color.isColor() || color.equals(RESET)) { + break; + } + } + } + } + + return result; + } + + static { + for (ChatColor color : values()) { + BY_ID.put(color.intCode, color); + BY_CHAR.put(color.code, color); + } + } +} diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java new file mode 100644 index 00000000..079b9feb --- /dev/null +++ b/src/main/java/org/bukkit/Chunk.java @@ -0,0 +1,133 @@ +package org.bukkit; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; + +/** + * Represents a chunk of blocks + */ +public interface Chunk { + + /** + * Gets the X-coordinate of this chunk + * + * @return X-coordinate + */ + int getX(); + + /** + * Gets the Z-coordinate of this chunk + * + * @return Z-coordinate + */ + int getZ(); + + /** + * Gets the world containing this chunk + * + * @return Parent World + */ + World getWorld(); + + /** + * Gets a block from this chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return the Block + */ + Block getBlock(int x, int y, int z); + + /** + * Capture thread-safe read-only snapshot of chunk data + * + * @return ChunkSnapshot + */ + ChunkSnapshot getChunkSnapshot(); + + /** + * Capture thread-safe read-only snapshot of chunk data + * + * @param includeMaxblocky - if true, snapshot includes per-coordinate + * maximum Y values + * @param includeBiome - if true, snapshot includes per-coordinate biome + * type + * @param includeBiomeTempRain - if true, snapshot includes per-coordinate + * raw biome temperature and rainfall + * @return ChunkSnapshot + */ + ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, boolean includeBiomeTempRain); + + /** + * Get a list of all entities in the chunk. + * + * @return The entities. + */ + Entity[] getEntities(); + + /** + * Get a list of all tile entities in the chunk. + * + * @return The tile entities. + */ + BlockState[] getTileEntities(); + + /** + * Checks if the chunk is loaded. + * + * @return True if it is loaded. + */ + boolean isLoaded(); + + /** + * Loads the chunk. + * + * @param generate Whether or not to generate a chunk if it doesn't + * already exist + * @return true if the chunk has loaded successfully, otherwise false + */ + boolean load(boolean generate); + + /** + * Loads the chunk. + * + * @return true if the chunk has loaded successfully, otherwise false + */ + boolean load(); + + /** + * Unloads and optionally saves the Chunk + * + * @param save Controls whether the chunk is saved + * @param safe Controls whether to unload the chunk when players are + * nearby + * @return true if the chunk has unloaded successfully, otherwise false + * @deprecated it is never safe to remove a chunk in use + */ + @Deprecated + boolean unload(boolean save, boolean safe); + + /** + * Unloads and optionally saves the Chunk + * + * @param save Controls whether the chunk is saved + * @return true if the chunk has unloaded successfully, otherwise false + */ + boolean unload(boolean save); + + /** + * Unloads and optionally saves the Chunk + * + * @return true if the chunk has unloaded successfully, otherwise false + */ + boolean unload(); + + /** + * Checks if this chunk can spawn slimes without being a swamp biome. + * + * @return true if slimes are able to spawn in this chunk + */ + boolean isSlimeChunk(); +} diff --git a/src/main/java/org/bukkit/ChunkSnapshot.java b/src/main/java/org/bukkit/ChunkSnapshot.java new file mode 100644 index 00000000..08e0bcdd --- /dev/null +++ b/src/main/java/org/bukkit/ChunkSnapshot.java @@ -0,0 +1,141 @@ +package org.bukkit; + +import org.bukkit.block.Biome; + +/** + * Represents a static, thread-safe snapshot of chunk of blocks. + *

+ * Purpose is to allow clean, efficient copy of a chunk data to be made, and + * then handed off for processing in another thread (e.g. map rendering) + */ +public interface ChunkSnapshot { + + /** + * Gets the X-coordinate of this chunk + * + * @return X-coordinate + */ + int getX(); + + /** + * Gets the Z-coordinate of this chunk + * + * @return Z-coordinate + */ + int getZ(); + + /** + * Gets name of the world containing this chunk + * + * @return Parent World Name + */ + String getWorldName(); + + /** + * Get block type for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return block material type + */ + Material getBlockType(int x, int y, int z); + + /** + * Get block type for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-255 + * @deprecated Magic value + */ + @Deprecated + int getBlockTypeId(int x, int y, int z); + + /** + * Get block data for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + * @deprecated Magic value + */ + @Deprecated + int getBlockData(int x, int y, int z); + + /** + * Get sky light level for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + int getBlockSkyLight(int x, int y, int z); + + /** + * Get light level emitted by block at corresponding coordinate in the + * chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + int getBlockEmittedLight(int x, int y, int z); + + /** + * Gets the highest non-air coordinate at the given coordinates + * + * @param x X-coordinate of the blocks + * @param z Z-coordinate of the blocks + * @return Y-coordinate of the highest non-air block + */ + int getHighestBlockYAt(int x, int z); + + /** + * Get biome at given coordinates + * + * @param x X-coordinate + * @param z Z-coordinate + * @return Biome at given coordinate + */ + Biome getBiome(int x, int z); + + /** + * Get raw biome temperature (0.0-1.0) at given coordinate + * + * @param x X-coordinate + * @param z Z-coordinate + * @return temperature at given coordinate + */ + double getRawBiomeTemperature(int x, int z); + + /** + * Get raw biome rainfall (0.0-1.0) at given coordinate + * + * @param x X-coordinate + * @param z Z-coordinate + * @return rainfall at given coordinate + * @deprecated this is not a chunk property in current Minecraft versions + */ + @Deprecated + double getRawBiomeRainfall(int x, int z); + + /** + * Get world full time when chunk snapshot was captured + * + * @return time in ticks + */ + long getCaptureFullTime(); + + /** + * Test if section is empty + * + * @param sy - section Y coordinate (block Y / 16) + * @return true if empty, false if not + */ + boolean isSectionEmpty(int sy); +} diff --git a/src/main/java/org/bukkit/CoalType.java b/src/main/java/org/bukkit/CoalType.java new file mode 100644 index 00000000..4fcccd21 --- /dev/null +++ b/src/main/java/org/bukkit/CoalType.java @@ -0,0 +1,50 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the two types of coal + */ +public enum CoalType { + COAL(0x0), + CHARCOAL(0x1); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private CoalType(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this type of coal + * + * @return A byte containing the data value of this coal type + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the type of coal with the given data value + * + * @param data Data value to fetch + * @return The {@link CoalType} representing the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static CoalType getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (CoalType type : values()) { + BY_DATA.put(type.data, type); + } + } +} diff --git a/src/main/java/org/bukkit/Color.java b/src/main/java/org/bukkit/Color.java new file mode 100644 index 00000000..052a2c76 --- /dev/null +++ b/src/main/java/org/bukkit/Color.java @@ -0,0 +1,344 @@ +package org.bukkit; + +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import com.google.common.collect.ImmutableMap; + +/** + * A container for a color palette. This class is immutable; the set methods + * return a new color. The color names listed as fields are HTML4 standards, + * but subject to change. + */ +@SerializableAs("Color") +public final class Color implements ConfigurationSerializable { + private static final int BIT_MASK = 0xff; + + /** + * White, or (0xFF,0xFF,0xFF) in (R,G,B) + */ + public static final Color WHITE = fromRGB(0xFFFFFF); + + /** + * Silver, or (0xC0,0xC0,0xC0) in (R,G,B) + */ + public static final Color SILVER = fromRGB(0xC0C0C0); + + /** + * Gray, or (0x80,0x80,0x80) in (R,G,B) + */ + public static final Color GRAY = fromRGB(0x808080); + + /** + * Black, or (0x00,0x00,0x00) in (R,G,B) + */ + public static final Color BLACK = fromRGB(0x000000); + + /** + * Red, or (0xFF,0x00,0x00) in (R,G,B) + */ + public static final Color RED = fromRGB(0xFF0000); + + /** + * Maroon, or (0x80,0x00,0x00) in (R,G,B) + */ + public static final Color MAROON = fromRGB(0x800000); + + /** + * Yellow, or (0xFF,0xFF,0x00) in (R,G,B) + */ + public static final Color YELLOW = fromRGB(0xFFFF00); + + /** + * Olive, or (0x80,0x80,0x00) in (R,G,B) + */ + public static final Color OLIVE = fromRGB(0x808000); + + /** + * Lime, or (0x00,0xFF,0x00) in (R,G,B) + */ + public static final Color LIME = fromRGB(0x00FF00); + + /** + * Green, or (0x00,0x80,0x00) in (R,G,B) + */ + public static final Color GREEN = fromRGB(0x008000); + + /** + * Aqua, or (0x00,0xFF,0xFF) in (R,G,B) + */ + public static final Color AQUA = fromRGB(0x00FFFF); + + /** + * Teal, or (0x00,0x80,0x80) in (R,G,B) + */ + public static final Color TEAL = fromRGB(0x008080); + + /** + * Blue, or (0x00,0x00,0xFF) in (R,G,B) + */ + public static final Color BLUE = fromRGB(0x0000FF); + + /** + * Navy, or (0x00,0x00,0x80) in (R,G,B) + */ + public static final Color NAVY = fromRGB(0x000080); + + /** + * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B) + */ + public static final Color FUCHSIA = fromRGB(0xFF00FF); + + /** + * Purple, or (0x80,0x00,0x80) in (R,G,B) + */ + public static final Color PURPLE = fromRGB(0x800080); + + /** + * Orange, or (0xFF,0xA5,0x00) in (R,G,B) + */ + public static final Color ORANGE = fromRGB(0xFFA500); + + private final byte red; + private final byte green; + private final byte blue; + + /** + * Creates a new Color object from a red, green, and blue + * + * @param red integer from 0-255 + * @param green integer from 0-255 + * @param blue integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly {@literal >255 or <0} + */ + public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new Color object from a blue, green, and red + * + * @param blue integer from 0-255 + * @param green integer from 0-255 + * @param red integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly {@literal >255 or <0} + */ + public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new color object from an integer that contains the red, + * green, and blue bytes in the lowest order 24 bits. + * + * @param rgb the integer storing the red, green, and blue values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 + * bits + */ + public static Color fromRGB(int rgb) throws IllegalArgumentException { + Validate.isTrue((rgb >> 24) == 0, "Extrenuous data in: ", rgb); + return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb >> 0 & BIT_MASK); + } + + /** + * Creates a new color object from an integer that contains the blue, + * green, and red bytes in the lowest order 24 bits. + * + * @param bgr the integer storing the blue, green, and red values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 + * bits + */ + public static Color fromBGR(int bgr) throws IllegalArgumentException { + Validate.isTrue((bgr >> 24) == 0, "Extrenuous data in: ", bgr); + return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr >> 0 & BIT_MASK); + } + + private Color(int red, int green, int blue) { + Validate.isTrue(red >= 0 && red <= BIT_MASK, "Red is not between 0-255: ", red); + Validate.isTrue(green >= 0 && green <= BIT_MASK, "Green is not between 0-255: ", green); + Validate.isTrue(blue >= 0 && blue <= BIT_MASK, "Blue is not between 0-255: ", blue); + + this.red = (byte) red; + this.green = (byte) green; + this.blue = (byte) blue; + } + + /** + * Gets the red component + * + * @return red component, from 0 to 255 + */ + public int getRed() { + return BIT_MASK & red; + } + + /** + * Creates a new Color object with specified component + * + * @param red the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setRed(int red) { + return fromRGB(red, getGreen(), getBlue()); + } + + /** + * Gets the green component + * + * @return green component, from 0 to 255 + */ + public int getGreen() { + return BIT_MASK & green; + } + + /** + * Creates a new Color object with specified component + * + * @param green the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setGreen(int green) { + return fromRGB(getRed(), green, getBlue()); + } + + /** + * Gets the blue component + * + * @return blue component, from 0 to 255 + */ + public int getBlue() { + return BIT_MASK & blue; + } + + /** + * Creates a new Color object with specified component + * + * @param blue the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setBlue(int blue) { + return fromRGB(getRed(), getGreen(), blue); + } + + /** + * + * @return An integer representation of this color, as 0xRRGGBB + */ + public int asRGB() { + return getRed() << 16 | getGreen() << 8 | getBlue() << 0; + } + + /** + * + * @return An integer representation of this color, as 0xBBGGRR + */ + public int asBGR() { + return getBlue() << 16 | getGreen() << 8 | getRed() << 0; + } + + /** + * Creates a new color with its RGB components changed as if it was dyed + * with the colors passed in, replicating vanilla workbench dyeing + * + * @param colors The DyeColors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + public Color mixDyes(DyeColor... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + Color[] toPass = new Color[colors.length]; + for (int i = 0; i < colors.length; i++) { + toPass[i] = colors[i].getColor(); + } + + return mixColors(toPass); + } + + /** + * Creates a new color with its RGB components changed as if it was dyed + * with the colors passed in, replicating vanilla workbench dyeing + * + * @param colors The colors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + public Color mixColors(Color... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + int totalRed = this.getRed(); + int totalGreen = this.getGreen(); + int totalBlue = this.getBlue(); + int totalMax = Math.max(Math.max(totalRed, totalGreen), totalBlue); + for (Color color : colors) { + totalRed += color.getRed(); + totalGreen += color.getGreen(); + totalBlue += color.getBlue(); + totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue()); + } + + float averageRed = totalRed / (colors.length + 1); + float averageGreen = totalGreen / (colors.length + 1); + float averageBlue = totalBlue / (colors.length + 1); + float averageMax = totalMax / (colors.length + 1); + + float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue); + float gainFactor = averageMax / maximumOfAverages; + + return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor)); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Color)) { + return false; + } + final Color that = (Color) o; + return this.blue == that.blue && this.green == that.green && this.red == that.red; + } + + @Override + public int hashCode() { + return asRGB() ^ Color.class.hashCode(); + } + + public Map serialize() { + return ImmutableMap.of( + "RED", getRed(), + "BLUE", getBlue(), + "GREEN", getGreen() + ); + } + + @SuppressWarnings("javadoc") + public static Color deserialize(Map map) { + return fromRGB( + asInt("RED", map), + asInt("GREEN", map), + asInt("BLUE", map) + ); + } + + private static int asInt(String string, Map map) { + Object value = map.get(string); + if (value == null) { + throw new IllegalArgumentException(string + " not in map " + map); + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException(string + '(' + value + ") is not a number"); + } + return ((Number) value).intValue(); + } + + @Override + public String toString() { + return "Color:[rgb0x" + Integer.toHexString(getRed()).toUpperCase() + Integer.toHexString(getGreen()).toUpperCase() + Integer.toHexString(getBlue()).toUpperCase() + "]"; + } +} diff --git a/src/main/java/org/bukkit/CropState.java b/src/main/java/org/bukkit/CropState.java new file mode 100644 index 00000000..ef0faf93 --- /dev/null +++ b/src/main/java/org/bukkit/CropState.java @@ -0,0 +1,81 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the different growth states of crops + */ +public enum CropState { + + /** + * State when first seeded + */ + SEEDED(0x0), + /** + * First growth stage + */ + GERMINATED(0x1), + /** + * Second growth stage + */ + VERY_SMALL(0x2), + /** + * Third growth stage + */ + SMALL(0x3), + /** + * Fourth growth stage + */ + MEDIUM(0x4), + /** + * Fifth growth stage + */ + TALL(0x5), + /** + * Almost ripe stage + */ + VERY_TALL(0x6), + /** + * Ripe stage + */ + RIPE(0x7); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private CropState(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this growth state + * + * @return A byte containing the data value of this growth state + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the CropState with the given data value + * + * @param data Data value to fetch + * @return The {@link CropState} representing the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static CropState getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (CropState cropState : values()) { + BY_DATA.put(cropState.getData(), cropState); + } + } +} diff --git a/src/main/java/org/bukkit/Difficulty.java b/src/main/java/org/bukkit/Difficulty.java new file mode 100644 index 00000000..a8a5a785 --- /dev/null +++ b/src/main/java/org/bukkit/Difficulty.java @@ -0,0 +1,72 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the various difficulty levels that are available. + */ +public enum Difficulty { + /** + * Players regain health over time, hostile mobs don't spawn, the hunger + * bar does not deplete. + */ + PEACEFUL(0), + + /** + * Hostile mobs spawn, enemies deal less damage than on normal difficulty, + * the hunger bar does deplete and starving deals up to 5 hearts of + * damage. (Default value) + */ + EASY(1), + + /** + * Hostile mobs spawn, enemies deal normal amounts of damage, the hunger + * bar does deplete and starving deals up to 9.5 hearts of damage. + */ + NORMAL(2), + + /** + * Hostile mobs spawn, enemies deal greater damage than on normal + * difficulty, the hunger bar does deplete and starving can kill players. + */ + HARD(3); + + private final int value; + private final static Map BY_ID = Maps.newHashMap(); + + private Difficulty(final int value) { + this.value = value; + } + + /** + * Gets the difficulty value associated with this Difficulty. + * + * @return An integer value of this difficulty + * @deprecated Magic value + */ + @Deprecated + public int getValue() { + return value; + } + + /** + * Gets the Difficulty represented by the specified value + * + * @param value Value to check + * @return Associative {@link Difficulty} with the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static Difficulty getByValue(final int value) { + return BY_ID.get(value); + } + + static { + for (Difficulty diff : values()) { + BY_ID.put(diff.value, diff); + } + } +} diff --git a/src/main/java/org/bukkit/DyeColor.java b/src/main/java/org/bukkit/DyeColor.java new file mode 100644 index 00000000..100d3e1e --- /dev/null +++ b/src/main/java/org/bukkit/DyeColor.java @@ -0,0 +1,209 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +/** + * All supported color values for dyes and cloth + */ +public enum DyeColor { + + /** + * Represents white dye. + */ + WHITE(0x0, 0xF, Color.fromRGB(0xF9FFFE), Color.fromRGB(0xF0F0F0)), + /** + * Represents orange dye. + */ + ORANGE(0x1, 0xE, Color.fromRGB(0xF9801D), Color.fromRGB(0xEB8844)), + /** + * Represents magenta dye. + */ + MAGENTA(0x2, 0xD, Color.fromRGB(0xC74EBD), Color.fromRGB(0xC354CD)), + /** + * Represents light blue dye. + */ + LIGHT_BLUE(0x3, 0xC, Color.fromRGB(0x3AB3DA), Color.fromRGB(0x6689D3)), + /** + * Represents yellow dye. + */ + YELLOW(0x4, 0xB, Color.fromRGB(0xFED83D), Color.fromRGB(0xDECF2A)), + /** + * Represents lime dye. + */ + LIME(0x5, 0xA, Color.fromRGB(0x80C71F), Color.fromRGB(0x41CD34)), + /** + * Represents pink dye. + */ + PINK(0x6, 0x9, Color.fromRGB(0xF38BAA), Color.fromRGB(0xD88198)), + /** + * Represents gray dye. + */ + GRAY(0x7, 0x8, Color.fromRGB(0x474F52), Color.fromRGB(0x434343)), + /** + * Represents silver dye. + */ + SILVER(0x8, 0x7, Color.fromRGB(0x9D9D97), Color.fromRGB(0xABABAB)), + /** + * Represents cyan dye. + */ + CYAN(0x9, 0x6, Color.fromRGB(0x169C9C), Color.fromRGB(0x287697)), + /** + * Represents purple dye. + */ + PURPLE(0xA, 0x5, Color.fromRGB(0x8932B8), Color.fromRGB(0x7B2FBE)), + /** + * Represents blue dye. + */ + BLUE(0xB, 0x4, Color.fromRGB(0x3C44AA), Color.fromRGB(0x253192)), + /** + * Represents brown dye. + */ + BROWN(0xC, 0x3, Color.fromRGB(0x835432), Color.fromRGB(0x51301A)), + /** + * Represents green dye. + */ + GREEN(0xD, 0x2, Color.fromRGB(0x5E7C16), Color.fromRGB(0x3B511A)), + /** + * Represents red dye. + */ + RED(0xE, 0x1, Color.fromRGB(0xB02E26), Color.fromRGB(0xB3312C)), + /** + * Represents black dye. + */ + BLACK(0xF, 0x0, Color.fromRGB(0x1D1D21), Color.fromRGB(0x1E1B1B)); + + private final byte woolData; + private final byte dyeData; + private final Color color; + private final Color firework; + private final static DyeColor[] BY_WOOL_DATA; + private final static DyeColor[] BY_DYE_DATA; + private final static Map BY_COLOR; + private final static Map BY_FIREWORK; + + private DyeColor(final int woolData, final int dyeData, Color color, Color firework) { + this.woolData = (byte) woolData; + this.dyeData = (byte) dyeData; + this.color = color; + this.firework = firework; + } + + /** + * Gets the associated wool data value representing this color. + * + * @return A byte containing the wool data value of this color + * @see #getDyeData() + * @deprecated Magic value + */ + @Deprecated + public byte getWoolData() { + return woolData; + } + + /** + * Gets the associated dye data value representing this color. + * + * @return A byte containing the dye data value of this color + * @see #getWoolData() + * @deprecated Magic value + */ + @Deprecated + public byte getDyeData() { + return dyeData; + } + + /** + * Gets the color that this dye represents. + * + * @return The {@link Color} that this dye represents + */ + public Color getColor() { + return color; + } + + /** + * Gets the firework color that this dye represents. + * + * @return The {@link Color} that this dye represents + */ + public Color getFireworkColor() { + return firework; + } + + /** + * Gets the DyeColor with the given wool data value. + * + * @param data Wool data value to fetch + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + * @see #getByDyeData(byte) + * @deprecated Magic value + */ + @Deprecated + public static DyeColor getByWoolData(final byte data) { + int i = 0xff & data; + if (i >= BY_WOOL_DATA.length) { + return null; + } + return BY_WOOL_DATA[i]; + } + + /** + * Gets the DyeColor with the given dye data value. + * + * @param data Dye data value to fetch + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + * @see #getByWoolData(byte) + * @deprecated Magic value + */ + @Deprecated + public static DyeColor getByDyeData(final byte data) { + int i = 0xff & data; + if (i >= BY_DYE_DATA.length) { + return null; + } + return BY_DYE_DATA[i]; + } + + /** + * Gets the DyeColor with the given color value. + * + * @param color Color value to get the dye by + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + */ + public static DyeColor getByColor(final Color color) { + return BY_COLOR.get(color); + } + + /** + * Gets the DyeColor with the given firework color value. + * + * @param color Color value to get dye by + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + */ + public static DyeColor getByFireworkColor(final Color color) { + return BY_FIREWORK.get(color); + } + + static { + BY_WOOL_DATA = values(); + BY_DYE_DATA = values(); + ImmutableMap.Builder byColor = ImmutableMap.builder(); + ImmutableMap.Builder byFirework = ImmutableMap.builder(); + + for (DyeColor color : values()) { + BY_WOOL_DATA[color.woolData & 0xff] = color; + BY_DYE_DATA[color.dyeData & 0xff] = color; + byColor.put(color.getColor(), color); + byFirework.put(color.getFireworkColor(), color); + } + + BY_COLOR = byColor.build(); + BY_FIREWORK = byFirework.build(); + } +} diff --git a/src/main/java/org/bukkit/Effect.java b/src/main/java/org/bukkit/Effect.java new file mode 100644 index 00000000..eeb25812 --- /dev/null +++ b/src/main/java/org/bukkit/Effect.java @@ -0,0 +1,528 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +import org.bukkit.block.BlockFace; +import org.bukkit.material.MaterialData; +import org.bukkit.potion.Potion; + +/** + * A list of effects that the server is able to send to players. + */ +public enum Effect { + /** + * An alternate click sound. + */ + CLICK2(1000, Type.SOUND), + /** + * A click sound. + */ + CLICK1(1001, Type.SOUND), + /** + * Sound of a bow firing. + */ + BOW_FIRE(1002, Type.SOUND), + /** + * Sound of a door opening. + */ + DOOR_TOGGLE(1006, Type.SOUND), + /** + * Sound of a door opening. + */ + IRON_DOOR_TOGGLE(1005, Type.SOUND), + /** + * Sound of a trapdoor opening. + */ + TRAPDOOR_TOGGLE(1007, Type.SOUND), + /** + * Sound of a door opening. + */ + IRON_TRAPDOOR_TOGGLE(1037, Type.SOUND), + /** + * Sound of a door opening. + */ + FENCE_GATE_TOGGLE(1008, Type.SOUND), + /** + * Sound of a door closing. + */ + DOOR_CLOSE(1012, Type.SOUND), + /** + * Sound of a door closing. + */ + IRON_DOOR_CLOSE(1011, Type.SOUND), + /** + * Sound of a trapdoor closing. + */ + TRAPDOOR_CLOSE(1013, Type.SOUND), + /** + * Sound of a door closing. + */ + IRON_TRAPDOOR_CLOSE(1036, Type.SOUND), + /** + * Sound of a door closing. + */ + FENCE_GATE_CLOSE(1014, Type.SOUND), + /** + * Sound of fire being extinguished. + */ + EXTINGUISH(1009, Type.SOUND), + /** + * A song from a record. Needs the record item ID as additional info + */ + RECORD_PLAY(1010, Type.SOUND, Material.class), + /** + * Sound of ghast shrieking. + */ + GHAST_SHRIEK(1015, Type.SOUND), + /** + * Sound of ghast firing. + */ + GHAST_SHOOT(1016, Type.SOUND), + /** + * Sound of blaze firing. + */ + BLAZE_SHOOT(1018, Type.SOUND), + /** + * Sound of zombies chewing on wooden doors. + */ + ZOMBIE_CHEW_WOODEN_DOOR(1019, Type.SOUND), + /** + * Sound of zombies chewing on iron doors. + */ + ZOMBIE_CHEW_IRON_DOOR(1020, Type.SOUND), + /** + * Sound of zombies destroying a door. + */ + ZOMBIE_DESTROY_DOOR(1021, Type.SOUND), + /** + * A visual smoke effect. Needs direction as additional info. + */ + SMOKE(2000, Type.VISUAL, BlockFace.class), + /** + * Sound of a block breaking. Needs block ID as additional info. + */ + STEP_SOUND(2001, Type.SOUND, Material.class), + /** + * Visual effect of a splash potion breaking. Needs potion data value as + * additional info. + */ + POTION_BREAK(2002, Type.VISUAL, Potion.class), + /** + * An ender eye signal; a visual effect. + */ + ENDER_SIGNAL(2003, Type.VISUAL), + /** + * The flames seen on a mobspawner; a visual effect. + */ + MOBSPAWNER_FLAMES(2004, Type.VISUAL), + /** + * The sound played by brewing stands when brewing + */ + BREWING_STAND_BREW(1035, Type.SOUND), + /** + * The sound played when a chorus flower grows + */ + CHORUS_FLOWER_GROW(1033, Type.SOUND), + /** + * The sound played when a chorus flower dies + */ + CHORUS_FLOWER_DEATH(1034, Type.SOUND), + /** + * The sound played when traveling through a portal + */ + PORTAL_TRAVEL(1032, Type.SOUND), + /** + * The sound played when launching an endereye + */ + ENDEREYE_LAUNCH(1003, Type.SOUND), + /** + * The sound played when launching a firework + */ + FIREWORK_SHOOT(1004, Type.SOUND), + /** + * Particles displayed when a villager grows a plant, data + * is the number of particles + */ + VILLAGER_PLANT_GROW(2005, Type.VISUAL, Integer.class), + /** + * The sound/particles used by the enderdragon's breath + * attack. + */ + DRAGON_BREATH(2006, Type.VISUAL), + /** + * The sound played when an anvil breaks + */ + ANVIL_BREAK(1029, Type.SOUND), + /** + * The sound played when an anvil is used + */ + ANVIL_USE(1030, Type.SOUND), + /** + * The sound played when an anvil lands after + * falling + */ + ANVIL_LAND(1031, Type.SOUND), + /** + * Sound of an enderdragon firing + */ + ENDERDRAGON_SHOOT(1017, Type.SOUND), + /** + * The sound played when a wither breaks a block + */ + WITHER_BREAK_BLOCK(1022, Type.SOUND), + /** + * Sound of a wither shooting + */ + WITHER_SHOOT(1024, Type.SOUND), + /** + * The sound played when a zombie infects a target + */ + ZOMBIE_INFECT(1026, Type.SOUND), + /** + * The sound played when a villager is converted by + * a zombie + */ + ZOMBIE_CONVERTED_VILLAGER(1027, Type.SOUND), + /** + * Sound played by a bat taking off + */ + BAT_TAKEOFF(1025, Type.SOUND), + /** + * The sound/particles caused by a end gateway spawning + */ + END_GATEWAY_SPAWN(3000, Type.VISUAL), + /** + * The sound of an enderdragon growling + */ + ENDERDRAGON_GROWL(3001, Type.SOUND), + /** + * The spark that comes off a fireworks + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + FIREWORKS_SPARK("fireworksSpark", Type.PARTICLE), + /** + * Critical hit particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + CRIT("crit", Type.PARTICLE), + /** + * Blue critical hit particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + MAGIC_CRIT("magicCrit", Type.PARTICLE), + /** + * Multicolored potion effect particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + POTION_SWIRL("mobSpell", Type.PARTICLE), + /** + * Multicolored potion effect particles that are slightly transparent + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + POTION_SWIRL_TRANSPARENT("mobSpellAmbient", Type.PARTICLE), + /** + * A puff of white potion swirls + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SPELL("spell", Type.PARTICLE), + /** + * A puff of white stars + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + INSTANT_SPELL("instantSpell", Type.PARTICLE), + /** + * A puff of purple particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + WITCH_MAGIC("witchMagic", Type.PARTICLE), + /** + * The note that appears above note blocks + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + NOTE("note", Type.PARTICLE), + /** + * The particles shown at nether portals + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + PORTAL("portal", Type.PARTICLE), + /** + * The symbols that fly towards the enchantment table + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + FLYING_GLYPH("enchantmenttable", Type.PARTICLE), + /** + * Fire particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + FLAME("flame", Type.PARTICLE), + /** + * The particles that pop out of lava + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + LAVA_POP("lava", Type.PARTICLE), + /** + * A small gray square + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + FOOTSTEP("footstep", Type.PARTICLE), + /** + * Water particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SPLASH("splash", Type.PARTICLE), + /** + * Smoke particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + PARTICLE_SMOKE("smoke", Type.PARTICLE), + /** + * The biggest explosion particle effect + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + EXPLOSION_HUGE("hugeexplosion", Type.PARTICLE), + /** + * A larger version of the explode particle + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + EXPLOSION_LARGE("largeexplode", Type.PARTICLE), + /** + * Explosion particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + EXPLOSION("explode", Type.PARTICLE), + /** + * Small gray particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + VOID_FOG("depthsuspend", Type.PARTICLE), + /** + * Small gray particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SMALL_SMOKE("townaura", Type.PARTICLE), + /** + * A puff of white smoke + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + CLOUD("cloud", Type.PARTICLE), + /** + * Multicolored dust particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + COLOURED_DUST("reddust", Type.PARTICLE), + /** + * Snowball breaking + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SNOWBALL_BREAK("snowballpoof", Type.PARTICLE), + /** + * The water drip particle that appears on blocks under water + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + WATERDRIP("dripWater", Type.PARTICLE), + /** + * The lava drip particle that appears on blocks under lava + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + LAVADRIP("dripLava", Type.PARTICLE), + /** + * White particles + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SNOW_SHOVEL("snowshovel", Type.PARTICLE), + /** + * The particle shown when a slime jumps + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + SLIME("slime", Type.PARTICLE), + /** + * The particle that appears when breading animals + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + HEART("heart", Type.PARTICLE), + /** + * The particle that appears when hitting a villager + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + VILLAGER_THUNDERCLOUD("angryVillager", Type.PARTICLE), + /** + * The particle that appears when trading with a villager + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + HAPPY_VILLAGER("happyVillager", Type.PARTICLE), + /** + * The smoke particles that appears on blazes, minecarts + * with furnaces and fire + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + LARGE_SMOKE("largesmoke", Type.PARTICLE), + /** + * The particles generated when a tool breaks. + * This particle requires a Material so that the client can select the correct texture. + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + ITEM_BREAK("iconcrack", Type.PARTICLE, Material.class), + /** + * The particles generated while breaking a block. + * This particle requires a Material and data value so that the client can select the correct texture. + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + TILE_BREAK("blockcrack", Type.PARTICLE, MaterialData.class), + /** + * The particles generated while sprinting a block + * This particle requires a Material and data value so that the client can select the correct texture. + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + TILE_DUST("blockdust", Type.PARTICLE, MaterialData.class) + ; + + private final int id; + private final Type type; + private final Class data; + private static final Map BY_ID = Maps.newHashMap(); + private static final Map BY_NAME = Maps.newHashMap(); + private final String particleName; + + private Effect(int id, Type type) { + this(id, type, null); + } + + private Effect(int id, Type type, Class data) { + this.id = id; + this.type = type; + this.data = data; + particleName = null; + } + + private Effect(String particleName, Type type, Class data) { + this.particleName = particleName; + this.type = type; + id = 0; + this.data = data; + } + + private Effect(String particleName, Type type) { + this.particleName = particleName; + this.type = type; + id = 0; + this.data = null; + } + + /** + * Gets the ID for this effect. + * + * @return if this Effect isn't of type PARTICLE it returns ID of this effect + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return this.id; + } + + /** + * Returns the effect's name. This returns null if the effect is not a particle + * + * @return The effect's name + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + public String getName() { + return particleName; + } + + /** + * @return The type of the effect. + */ + public Type getType() { + return this.type; + } + + /** + * @return if this Effect isn't of type PARTICLE it returns the class which represents data for this effect, or null if none + */ + public Class getData() { + return this.data; + } + + /** + * Gets the Effect associated with the given ID. + * + * @param id ID of the Effect to return + * @return Effect with the given ID + * @deprecated Magic value + */ + @Deprecated + public static Effect getById(int id) { + return BY_ID.get(id); + } + + static { + for (Effect effect : values()) { + if (effect.type != Type.PARTICLE) { + BY_ID.put(effect.id, effect); + } + } + } + + /** + * Gets the Effect associated with the given name. + * + * @param name name of the Effect to return + * @return Effect with the given name + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + public static Effect getByName(String name) { + return BY_NAME.get(name); + } + + static { + for (Effect effect : values()) { + if (effect.type == Type.PARTICLE) { + BY_NAME.put(effect.particleName, effect); + } + } + } + + /** + * Represents the type of an effect. + */ + public enum Type {SOUND, VISUAL, PARTICLE} +} diff --git a/src/main/java/org/bukkit/EntityEffect.java b/src/main/java/org/bukkit/EntityEffect.java new file mode 100644 index 00000000..5d234350 --- /dev/null +++ b/src/main/java/org/bukkit/EntityEffect.java @@ -0,0 +1,199 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.bukkit.entity.Ageable; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Guardian; +import org.bukkit.entity.IronGolem; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Rabbit; +import org.bukkit.entity.Squid; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.TippedArrow; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Witch; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.ZombieVillager; + +/** + * A list of all Effects that can happen to entities. + */ +public enum EntityEffect { + + /** + * Colored particles from a tipped arrow. + */ + ARROW_PARTICLES(0, TippedArrow.class), + /** + * Rabbit jumping. + */ + RABBIT_JUMP(1, Rabbit.class), + /** + * When mobs get hurt. + */ + HURT(2, LivingEntity.class), + /** + * When a mob dies. + *

+ * This will cause client-glitches! + * + * @deprecated although this effect may trigger other events on non-living + * entities, it's only supported usage is on living ones. + */ + @Deprecated + DEATH(3, Entity.class), + // PAIL - SPIGOT-3641 duplicate + // GOLEM_ATTACK(4, IronGolem.class), + // 5 - unused + /** + * The smoke when taming a wolf fails. + */ + WOLF_SMOKE(6, Tameable.class), + /** + * The hearts when taming a wolf succeeds. + */ + WOLF_HEARTS(7, Wolf.class), + /** + * When a wolf shakes (after being wet). + */ + WOLF_SHAKE(8, Wolf.class), + // 9 - unused + /** + * When an entity eats a LONG_GRASS block. + * + * @deprecated although this effect may trigger other events on non-living + * entities, it's only supported usage is on living ones. + */ + @Deprecated + SHEEP_EAT(10, Entity.class), + /** + * When an Iron Golem gives a rose. + */ + IRON_GOLEM_ROSE(11, IronGolem.class), + /** + * Hearts from a villager. + */ + VILLAGER_HEART(12, Villager.class), + /** + * When a villager is angry. + */ + VILLAGER_ANGRY(13, Villager.class), + /** + * Happy particles from a villager. + */ + VILLAGER_HAPPY(14, Villager.class), + /** + * Magic particles from a witch. + */ + WITCH_MAGIC(15, Witch.class), + /** + * When a zombie transforms into a villager by shaking violently. + */ + ZOMBIE_TRANSFORM(16, ZombieVillager.class), + /** + * When a firework explodes. + */ + FIREWORK_EXPLODE(17, Firework.class), + /** + * Hearts from a breeding entity. + */ + LOVE_HEARTS(18, Ageable.class), + /** + * Resets squid rotation. + */ + SQUID_ROTATE(19, Squid.class), + /** + * Silverfish entering block, spawner spawning. + */ + ENTITY_POOF(20, LivingEntity.class), + /** + * Guardian sets laser target. + */ + GUARDIAN_TARGET(21, Guardian.class), + // 22-28 player internal flags + /** + * Shield blocks attack. + */ + SHIELD_BLOCK(29, LivingEntity.class), + /** + * Shield breaks. + */ + SHIELD_BREAK(30, LivingEntity.class), + // 31 - unused + /** + * Armor stand is hit. + */ + ARMOR_STAND_HIT(32, ArmorStand.class), + /** + * Entity hurt by thorns attack. + */ + THORNS_HURT(33, LivingEntity.class), + /** + * Iron golem puts away rose. + */ + IRON_GOLEM_SHEATH(34, IronGolem.class), + /** + * Totem prevents entity death. + */ + TOTEM_RESURRECT(35, LivingEntity.class), + /** + * Entity hurt due to drowning damage. + */ + HURT_DROWN(36, LivingEntity.class), + /** + * Entity hurt due to explosion damage. + */ + HURT_EXPLOSION(37, LivingEntity.class); + + private final byte data; + private final Class applicable; + private final static Map BY_DATA = Maps.newHashMap(); + + EntityEffect(final int data, Class clazz) { + this.data = (byte) data; + this.applicable = clazz; + } + + /** + * Gets the data value of this EntityEffect + * + * @return The data value + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets entity superclass which this affect is applicable to. + * + * @return applicable class + */ + public Class getApplicable() { + return applicable; + } + + /** + * Gets the EntityEffect with the given data value + * + * @param data Data value to fetch + * @return The {@link EntityEffect} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static EntityEffect getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (EntityEffect entityEffect : values()) { + BY_DATA.put(entityEffect.data, entityEffect); + } + } +} diff --git a/src/main/java/org/bukkit/FireworkEffect.java b/src/main/java/org/bukkit/FireworkEffect.java new file mode 100644 index 00000000..e6940dd2 --- /dev/null +++ b/src/main/java/org/bukkit/FireworkEffect.java @@ -0,0 +1,421 @@ +package org.bukkit; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Represents a single firework effect. + */ +@SerializableAs("Firework") +public final class FireworkEffect implements ConfigurationSerializable { + + /** + * The type or shape of the effect. + */ + public enum Type { + /** + * A small ball effect. + */ + BALL, + /** + * A large ball effect. + */ + BALL_LARGE, + /** + * A star-shaped effect. + */ + STAR, + /** + * A burst effect. + */ + BURST, + /** + * A creeper-face effect. + */ + CREEPER, + ; + } + + /** + * Construct a firework effect. + * + * @return A utility object for building a firework effect + */ + public static Builder builder() { + return new Builder(); + } + + /** + * This is a builder for FireworkEffects. + * + * @see FireworkEffect#builder() + */ + public static final class Builder { + boolean flicker = false; + boolean trail = false; + final ImmutableList.Builder colors = ImmutableList.builder(); + ImmutableList.Builder fadeColors = null; + Type type = Type.BALL; + + Builder() {} + + /** + * Specify the type of the firework effect. + * + * @param type The effect type + * @return This object, for chaining + * @throws IllegalArgumentException If type is null + */ + public Builder with(Type type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null type"); + this.type = type; + return this; + } + + /** + * Add a flicker to the firework effect. + * + * @return This object, for chaining + */ + public Builder withFlicker() { + flicker = true; + return this; + } + + /** + * Set whether the firework effect should flicker. + * + * @param flicker true if it should flicker, false if not + * @return This object, for chaining + */ + public Builder flicker(boolean flicker) { + this.flicker = flicker; + return this; + } + + /** + * Add a trail to the firework effect. + * + * @return This object, for chaining + */ + public Builder withTrail() { + trail = true; + return this; + } + + /** + * Set whether the firework effect should have a trail. + * + * @param trail true if it should have a trail, false for no trail + * @return This object, for chaining + */ + public Builder trail(boolean trail) { + this.trail = trail; + return this; + } + + /** + * Add a primary color to the firework effect. + * + * @param color The color to add + * @return This object, for chaining + * @throws IllegalArgumentException If color is null + */ + public Builder withColor(Color color) throws IllegalArgumentException { + Validate.notNull(color, "Cannot have null color"); + + colors.add(color); + + return this; + } + + /** + * Add several primary colors to the firework effect. + * + * @param colors The colors to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + public Builder withColor(Color... colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + if (colors.length == 0) { + return this; + } + + ImmutableList.Builder list = this.colors; + for (Color color : colors) { + Validate.notNull(color, "Color cannot be null"); + list.add(color); + } + + return this; + } + + /** + * Add several primary colors to the firework effect. + * + * @param colors An iterable object whose iterator yields the desired + * colors + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + public Builder withColor(Iterable colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + + ImmutableList.Builder list = this.colors; + for (Object color : colors) { + if (!(color instanceof Color)) { + throw new IllegalArgumentException(color + " is not a Color in " + colors); + } + list.add((Color) color); + } + + return this; + } + + /** + * Add a fade color to the firework effect. + * + * @param color The color to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + public Builder withFade(Color color) throws IllegalArgumentException { + Validate.notNull(color, "Cannot have null color"); + + if (fadeColors == null) { + fadeColors = ImmutableList.builder(); + } + + fadeColors.add(color); + + return this; + } + + /** + * Add several fade colors to the firework effect. + * + * @param colors The colors to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + public Builder withFade(Color... colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + if (colors.length == 0) { + return this; + } + + ImmutableList.Builder list = this.fadeColors; + if (list == null) { + list = this.fadeColors = ImmutableList.builder(); + } + + for (Color color : colors) { + Validate.notNull(color, "Color cannot be null"); + list.add(color); + } + + return this; + } + + /** + * Add several fade colors to the firework effect. + * + * @param colors An iterable object whose iterator yields the desired + * colors + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + public Builder withFade(Iterable colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + + ImmutableList.Builder list = this.fadeColors; + if (list == null) { + list = this.fadeColors = ImmutableList.builder(); + } + + for (Object color : colors) { + if (!(color instanceof Color)) { + throw new IllegalArgumentException(color + " is not a Color in " + colors); + } + list.add((Color) color); + } + + return this; + } + + /** + * Create a {@link FireworkEffect} from the current contents of this + * builder. + *

+ * To successfully build, you must have specified at least one color. + * + * @return The representative firework effect + */ + public FireworkEffect build() { + return new FireworkEffect( + flicker, + trail, + colors.build(), + fadeColors == null ? ImmutableList.of() : fadeColors.build(), + type + ); + } + } + + private static final String FLICKER = "flicker"; + private static final String TRAIL = "trail"; + private static final String COLORS = "colors"; + private static final String FADE_COLORS = "fade-colors"; + private static final String TYPE = "type"; + + private final boolean flicker; + private final boolean trail; + private final ImmutableList colors; + private final ImmutableList fadeColors; + private final Type type; + private String string = null; + + FireworkEffect(boolean flicker, boolean trail, ImmutableList colors, ImmutableList fadeColors, Type type) { + if (colors.isEmpty()) { + throw new IllegalStateException("Cannot make FireworkEffect without any color"); + } + this.flicker = flicker; + this.trail = trail; + this.colors = colors; + this.fadeColors = fadeColors; + this.type = type; + } + + /** + * Get whether the firework effect flickers. + * + * @return true if it flickers, false if not + */ + public boolean hasFlicker() { + return flicker; + } + + /** + * Get whether the firework effect has a trail. + * + * @return true if it has a trail, false if not + */ + public boolean hasTrail() { + return trail; + } + + /** + * Get the primary colors of the firework effect. + * + * @return An immutable list of the primary colors + */ + public List getColors() { + return colors; + } + + /** + * Get the fade colors of the firework effect. + * + * @return An immutable list of the fade colors + */ + public List getFadeColors() { + return fadeColors; + } + + /** + * Get the type of the firework effect. + * + * @return The effect type + */ + public Type getType() { + return type; + } + + /** + * @see ConfigurationSerializable + * @param map the map to deserialize + * @return the resulting serializable + */ + public static ConfigurationSerializable deserialize(Map map) { + Type type = Type.valueOf((String) map.get(TYPE)); + + return builder() + .flicker((Boolean) map.get(FLICKER)) + .trail((Boolean) map.get(TRAIL)) + .withColor((Iterable) map.get(COLORS)) + .withFade((Iterable) map.get(FADE_COLORS)) + .with(type) + .build(); + } + + @Override + public Map serialize() { + return ImmutableMap.of( + FLICKER, flicker, + TRAIL, trail, + COLORS, colors, + FADE_COLORS, fadeColors, + TYPE, type.name() + ); + } + + @Override + public String toString() { + final String string = this.string; + if (string == null) { + return this.string = "FireworkEffect:" + serialize(); + } + return string; + } + + @Override + public int hashCode() { + /** + * TRUE and FALSE as per boolean.hashCode() + */ + final int PRIME = 31, TRUE = 1231, FALSE = 1237; + int hash = 1; + hash = hash * PRIME + (flicker ? TRUE : FALSE); + hash = hash * PRIME + (trail ? TRUE : FALSE); + hash = hash * PRIME + type.hashCode(); + hash = hash * PRIME + colors.hashCode(); + hash = hash * PRIME + fadeColors.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof FireworkEffect)) { + return false; + } + + FireworkEffect that = (FireworkEffect) obj; + return this.flicker == that.flicker + && this.trail == that.trail + && this.type == that.type + && this.colors.equals(that.colors) + && this.fadeColors.equals(that.fadeColors); + } +} diff --git a/src/main/java/org/bukkit/GameMode.java b/src/main/java/org/bukkit/GameMode.java new file mode 100644 index 00000000..803944ea --- /dev/null +++ b/src/main/java/org/bukkit/GameMode.java @@ -0,0 +1,73 @@ +package org.bukkit; + +import java.util.Map; + +import org.bukkit.entity.HumanEntity; + +import com.google.common.collect.Maps; + +/** + * Represents the various type of game modes that {@link HumanEntity}s may + * have + */ +public enum GameMode { + /** + * Creative mode may fly, build instantly, become invulnerable and create + * free items. + */ + CREATIVE(1), + + /** + * Survival mode is the "normal" gameplay type, with no special features. + */ + SURVIVAL(0), + + /** + * Adventure mode cannot break blocks without the correct tools. + */ + ADVENTURE(2), + + /** + * Spectator mode cannot interact with the world in anyway and is + * invisible to normal players. This grants the player the + * ability to no-clip through the world. + */ + SPECTATOR(3); + + private final int value; + private final static Map BY_ID = Maps.newHashMap(); + + private GameMode(final int value) { + this.value = value; + } + + /** + * Gets the mode value associated with this GameMode + * + * @return An integer value of this gamemode + * @deprecated Magic value + */ + @Deprecated + public int getValue() { + return value; + } + + /** + * Gets the GameMode represented by the specified value + * + * @param value Value to check + * @return Associative {@link GameMode} with the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static GameMode getByValue(final int value) { + return BY_ID.get(value); + } + + static { + for (GameMode mode : values()) { + BY_ID.put(mode.getValue(), mode); + } + } +} diff --git a/src/main/java/org/bukkit/GrassSpecies.java b/src/main/java/org/bukkit/GrassSpecies.java new file mode 100644 index 00000000..11115157 --- /dev/null +++ b/src/main/java/org/bukkit/GrassSpecies.java @@ -0,0 +1,61 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the different types of grass. + */ +public enum GrassSpecies { + + /** + * Represents the dead looking grass. + */ + DEAD(0x0), + /** + * Represents the normal grass species. + */ + NORMAL(0x1), + /** + * Represents the fern-looking grass species. + */ + FERN_LIKE(0x2); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private GrassSpecies(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this species + * + * @return A byte containing the data value of this grass species + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the GrassSpecies with the given data value + * + * @param data Data value to fetch + * @return The {@link GrassSpecies} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static GrassSpecies getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (GrassSpecies grassSpecies : values()) { + BY_DATA.put(grassSpecies.getData(), grassSpecies); + } + } +} diff --git a/src/main/java/org/bukkit/Instrument.java b/src/main/java/org/bukkit/Instrument.java new file mode 100644 index 00000000..14cfaca0 --- /dev/null +++ b/src/main/java/org/bukkit/Instrument.java @@ -0,0 +1,88 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +public enum Instrument { + + /** + * Piano is the standard instrument for a note block. + */ + PIANO(0x0), + /** + * Bass drum is normally played when a note block is on top of a + * stone-like block. + */ + BASS_DRUM(0x1), + /** + * Snare drum is normally played when a note block is on top of a sandy + * block. + */ + SNARE_DRUM(0x2), + /** + * Sticks are normally played when a note block is on top of a glass + * block. + */ + STICKS(0x3), + /** + * Bass guitar is normally played when a note block is on top of a wooden + * block. + */ + BASS_GUITAR(0x4), + /** + * Flute is normally played when a note block is on top of a clay block. + */ + FLUTE(0x5), + /** + * Bell is normally played when a note block is on top of a gold block. + */ + BELL(0x6), + /** + * Guitar is normally played when a note block is on top of a woolen block. + */ + GUITAR(0x7), + /** + * Chime is normally played when a note block is on top of a packed ice + * block. + */ + CHIME(0x8), + /** + * Xylophone is normally played when a note block is on top of a bone block. + */ + XYLOPHONE(0x9); + + private final byte type; + private final static Map BY_DATA = Maps.newHashMap(); + + private Instrument(final int type) { + this.type = (byte) type; + } + + /** + * @return The type ID of this instrument. + * @deprecated Magic value + */ + @Deprecated + public byte getType() { + return this.type; + } + + /** + * Get an instrument by its type ID. + * + * @param type The type ID + * @return The instrument + * @deprecated Magic value + */ + @Deprecated + public static Instrument getByType(final byte type) { + return BY_DATA.get(type); + } + + static { + for (Instrument instrument : Instrument.values()) { + BY_DATA.put(instrument.getType(), instrument); + } + } +} diff --git a/src/main/java/org/bukkit/Keyed.java b/src/main/java/org/bukkit/Keyed.java new file mode 100644 index 00000000..f1a7913b --- /dev/null +++ b/src/main/java/org/bukkit/Keyed.java @@ -0,0 +1,14 @@ +package org.bukkit; + +/** + * Represents an object which has a {@link NamespacedKey} attached to it. + */ +public interface Keyed { + + /** + * Return the namespaced identifier for this object. + * + * @return this object's key + */ + NamespacedKey getKey(); +} diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java new file mode 100644 index 00000000..5c3d42cc --- /dev/null +++ b/src/main/java/org/bukkit/Location.java @@ -0,0 +1,617 @@ +package org.bukkit; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.block.Block; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; + +/** + * Represents a 3-dimensional position in a world. + *
+ * No constraints are placed on any angular values other than that they be + * specified in degrees. This means that negative angles or angles of greater + * magnitude than 360 are valid, but may be normalized to any other equivalent + * representation by the implementation. + */ +public class Location implements Cloneable, ConfigurationSerializable { + private World world; + private double x; + private double y; + private double z; + private float pitch; + private float yaw; + + /** + * Constructs a new Location with the given coordinates + * + * @param world The world in which this location resides + * @param x The x-coordinate of this new location + * @param y The y-coordinate of this new location + * @param z The z-coordinate of this new location + */ + public Location(final World world, final double x, final double y, final double z) { + this(world, x, y, z, 0, 0); + } + + /** + * Constructs a new Location with the given coordinates and direction + * + * @param world The world in which this location resides + * @param x The x-coordinate of this new location + * @param y The y-coordinate of this new location + * @param z The z-coordinate of this new location + * @param yaw The absolute rotation on the x-plane, in degrees + * @param pitch The absolute rotation on the y-plane, in degrees + */ + public Location(final World world, final double x, final double y, final double z, final float yaw, final float pitch) { + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.pitch = pitch; + this.yaw = yaw; + } + + /** + * Sets the world that this location resides in + * + * @param world New world that this location resides in + */ + public void setWorld(World world) { + this.world = world; + } + + /** + * Gets the world that this location resides in + * + * @return World that contains this location + */ + public World getWorld() { + return world; + } + + /** + * Gets the chunk at the represented location + * + * @return Chunk at the represented location + */ + public Chunk getChunk() { + return world.getChunkAt(this); + } + + /** + * Gets the block at the represented location + * + * @return Block at the represented location + */ + public Block getBlock() { + return world.getBlockAt(this); + } + + /** + * Sets the x-coordinate of this location + * + * @param x X-coordinate + */ + public void setX(double x) { + this.x = x; + } + + /** + * Gets the x-coordinate of this location + * + * @return x-coordinate + */ + public double getX() { + return x; + } + + /** + * Gets the floored value of the X component, indicating the block that + * this location is contained with. + * + * @return block X + */ + public int getBlockX() { + return locToBlock(x); + } + + /** + * Sets the y-coordinate of this location + * + * @param y y-coordinate + */ + public void setY(double y) { + this.y = y; + } + + /** + * Gets the y-coordinate of this location + * + * @return y-coordinate + */ + public double getY() { + return y; + } + + /** + * Gets the floored value of the Y component, indicating the block that + * this location is contained with. + * + * @return block y + */ + public int getBlockY() { + return locToBlock(y); + } + + /** + * Sets the z-coordinate of this location + * + * @param z z-coordinate + */ + public void setZ(double z) { + this.z = z; + } + + /** + * Gets the z-coordinate of this location + * + * @return z-coordinate + */ + public double getZ() { + return z; + } + + /** + * Gets the floored value of the Z component, indicating the block that + * this location is contained with. + * + * @return block z + */ + public int getBlockZ() { + return locToBlock(z); + } + + /** + * Sets the yaw of this location, measured in degrees. + *

    + *
  • A yaw of 0 or 360 represents the positive z direction. + *
  • A yaw of 180 represents the negative z direction. + *
  • A yaw of 90 represents the negative x direction. + *
  • A yaw of 270 represents the positive x direction. + *
+ * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. + * + * @param yaw new rotation's yaw + */ + public void setYaw(float yaw) { + this.yaw = yaw; + } + + /** + * Gets the yaw of this location, measured in degrees. + *
    + *
  • A yaw of 0 or 360 represents the positive z direction. + *
  • A yaw of 180 represents the negative z direction. + *
  • A yaw of 90 represents the negative x direction. + *
  • A yaw of 270 represents the positive x direction. + *
+ * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. + * + * @return the rotation's yaw + */ + public float getYaw() { + return yaw; + } + + /** + * Sets the pitch of this location, measured in degrees. + *
    + *
  • A pitch of 0 represents level forward facing. + *
  • A pitch of 90 represents downward facing, or negative y + * direction. + *
  • A pitch of -90 represents upward facing, or positive y direction. + *
+ * Increasing pitch values the equivalent of looking down. + * + * @param pitch new incline's pitch + */ + public void setPitch(float pitch) { + this.pitch = pitch; + } + + /** + * Gets the pitch of this location, measured in degrees. + *
    + *
  • A pitch of 0 represents level forward facing. + *
  • A pitch of 90 represents downward facing, or negative y + * direction. + *
  • A pitch of -90 represents upward facing, or positive y direction. + *
+ * Increasing pitch values the equivalent of looking down. + * + * @return the incline's pitch + */ + public float getPitch() { + return pitch; + } + + /** + * Gets a unit-vector pointing in the direction that this Location is + * facing. + * + * @return a vector pointing the direction of this location's {@link + * #getPitch() pitch} and {@link #getYaw() yaw} + */ + public Vector getDirection() { + Vector vector = new Vector(); + + double rotX = this.getYaw(); + double rotY = this.getPitch(); + + vector.setY(-Math.sin(Math.toRadians(rotY))); + + double xz = Math.cos(Math.toRadians(rotY)); + + vector.setX(-xz * Math.sin(Math.toRadians(rotX))); + vector.setZ(xz * Math.cos(Math.toRadians(rotX))); + + return vector; + } + + /** + * Sets the {@link #getYaw() yaw} and {@link #getPitch() pitch} to point + * in the direction of the vector. + * + * @param vector the direction vector + * @return the same location + */ + public Location setDirection(Vector vector) { + /* + * Sin = Opp / Hyp + * Cos = Adj / Hyp + * Tan = Opp / Adj + * + * x = -Opp + * z = Adj + */ + final double _2PI = 2 * Math.PI; + final double x = vector.getX(); + final double z = vector.getZ(); + + if (x == 0 && z == 0) { + pitch = vector.getY() > 0 ? -90 : 90; + return this; + } + + double theta = Math.atan2(-x, z); + yaw = (float) Math.toDegrees((theta + _2PI) % _2PI); + + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + pitch = (float) Math.toDegrees(Math.atan(-vector.getY() / xz)); + + return this; + } + + /** + * Adds the location by another. + * + * @see Vector + * @param vec The other location + * @return the same location + * @throws IllegalArgumentException for differing worlds + */ + public Location add(Location vec) { + if (vec == null || vec.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot add Locations of differing worlds"); + } + + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * Adds the location by a vector. + * + * @see Vector + * @param vec Vector to use + * @return the same location + */ + public Location add(Vector vec) { + this.x += vec.getX(); + this.y += vec.getY(); + this.z += vec.getZ(); + return this; + } + + /** + * Adds the location by another. Not world-aware. + * + * @see Vector + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return the same location + */ + public Location add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + /** + * Subtracts the location by another. + * + * @see Vector + * @param vec The other location + * @return the same location + * @throws IllegalArgumentException for differing worlds + */ + public Location subtract(Location vec) { + if (vec == null || vec.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot add Locations of differing worlds"); + } + + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * Subtracts the location by a vector. + * + * @see Vector + * @param vec The vector to use + * @return the same location + */ + public Location subtract(Vector vec) { + this.x -= vec.getX(); + this.y -= vec.getY(); + this.z -= vec.getZ(); + return this; + } + + /** + * Subtracts the location by another. Not world-aware and + * orientation independent. + * + * @see Vector + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return the same location + */ + public Location subtract(double x, double y, double z) { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + /** + * Gets the magnitude of the location, defined as sqrt(x^2+y^2+z^2). The + * value of this method is not cached and uses a costly square-root + * function, so do not repeatedly call this method to get the location's + * magnitude. NaN will be returned if the inner result of the sqrt() + * function overflows, which will be caused if the length is too long. Not + * world-aware and orientation independent. + * + * @see Vector + * @return the magnitude + */ + public double length() { + return Math.sqrt(NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z)); + } + + /** + * Gets the magnitude of the location squared. Not world-aware and + * orientation independent. + * + * @see Vector + * @return the magnitude + */ + public double lengthSquared() { + return NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z); + } + + /** + * Get the distance between this location and another. The value of this + * method is not cached and uses a costly square-root function, so do not + * repeatedly call this method to get the location's magnitude. NaN will + * be returned if the inner result of the sqrt() function overflows, which + * will be caused if the distance is too long. + * + * @see Vector + * @param o The other location + * @return the distance + * @throws IllegalArgumentException for differing worlds + */ + public double distance(Location o) { + return Math.sqrt(distanceSquared(o)); + } + + /** + * Get the squared distance between this location and another. + * + * @see Vector + * @param o The other location + * @return the distance + * @throws IllegalArgumentException for differing worlds + */ + public double distanceSquared(Location o) { + if (o == null) { + throw new IllegalArgumentException("Cannot measure distance to a null location"); + } else if (o.getWorld() == null || getWorld() == null) { + throw new IllegalArgumentException("Cannot measure distance to a null world"); + } else if (o.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot measure distance between " + getWorld().getName() + " and " + o.getWorld().getName()); + } + + return NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z); + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. Not world-aware. + * + * @param m The factor + * @see Vector + * @return the same location + */ + public Location multiply(double m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Zero this location's components. Not world-aware. + * + * @see Vector + * @return the same location + */ + public Location zero() { + x = 0; + y = 0; + z = 0; + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Location other = (Location) obj; + + if (this.world != other.world && (this.world == null || !this.world.equals(other.world))) { + return false; + } + if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { + return false; + } + if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { + return false; + } + if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) { + return false; + } + if (Float.floatToIntBits(this.pitch) != Float.floatToIntBits(other.pitch)) { + return false; + } + if (Float.floatToIntBits(this.yaw) != Float.floatToIntBits(other.yaw)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + + hash = 19 * hash + (this.world != null ? this.world.hashCode() : 0); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); + hash = 19 * hash + Float.floatToIntBits(this.pitch); + hash = 19 * hash + Float.floatToIntBits(this.yaw); + return hash; + } + + @Override + public String toString() { + return "Location{" + "world=" + world + ",x=" + x + ",y=" + y + ",z=" + z + ",pitch=" + pitch + ",yaw=" + yaw + '}'; + } + + /** + * Constructs a new {@link Vector} based on this Location + * + * @return New Vector containing the coordinates represented by this + * Location + */ + public Vector toVector() { + return new Vector(x, y, z); + } + + @Override + public Location clone() { + try { + return (Location) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + /** + * Check if each component of this Location is finite. + * + * @throws IllegalArgumentException if any component is not finite + */ + public void checkFinite() throws IllegalArgumentException { + NumberConversions.checkFinite(x, "x not finite"); + NumberConversions.checkFinite(y, "y not finite"); + NumberConversions.checkFinite(z, "z not finite"); + NumberConversions.checkFinite(pitch, "pitch not finite"); + NumberConversions.checkFinite(yaw, "yaw not finite"); + } + + /** + * Safely converts a double (location coordinate) to an int (block + * coordinate) + * + * @param loc Precise coordinate + * @return Block coordinate + */ + public static int locToBlock(double loc) { + return NumberConversions.floor(loc); + } + + @Utility + public Map serialize() { + Map data = new HashMap(); + data.put("world", this.world.getName()); + + data.put("x", this.x); + data.put("y", this.y); + data.put("z", this.z); + + data.put("yaw", this.yaw); + data.put("pitch", this.pitch); + + return data; + } + + /** + * Required method for deserialization + * + * @param args map to deserialize + * @return deserialized location + * @throws IllegalArgumentException if the world don't exists + * @see ConfigurationSerializable + */ + public static Location deserialize(Map args) { + World world = Bukkit.getWorld((String) args.get("world")); + if (world == null) { + throw new IllegalArgumentException("unknown world"); + } + + return new Location(world, NumberConversions.toDouble(args.get("x")), NumberConversions.toDouble(args.get("y")), NumberConversions.toDouble(args.get("z")), NumberConversions.toFloat(args.get("yaw")), NumberConversions.toFloat(args.get("pitch"))); + } +} diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java new file mode 100644 index 00000000..777cd5f8 --- /dev/null +++ b/src/main/java/org/bukkit/Material.java @@ -0,0 +1,1490 @@ +package org.bukkit; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.map.MapView; +import org.bukkit.material.Bed; +import org.bukkit.material.Button; +import org.bukkit.material.Cake; +import org.bukkit.material.Cauldron; +import org.bukkit.material.Chest; +import org.bukkit.material.Coal; +import org.bukkit.material.CocoaPlant; +import org.bukkit.material.Command; +import org.bukkit.material.Comparator; +import org.bukkit.material.Crops; +import org.bukkit.material.DetectorRail; +import org.bukkit.material.Diode; +import org.bukkit.material.Dispenser; +import org.bukkit.material.Door; +import org.bukkit.material.Dye; +import org.bukkit.material.EnderChest; +import org.bukkit.material.FlowerPot; +import org.bukkit.material.Furnace; +import org.bukkit.material.Gate; +import org.bukkit.material.Hopper; +import org.bukkit.material.Ladder; +import org.bukkit.material.Leaves; +import org.bukkit.material.Lever; +import org.bukkit.material.LongGrass; +import org.bukkit.material.MaterialData; +import org.bukkit.material.MonsterEggs; +import org.bukkit.material.Mushroom; +import org.bukkit.material.NetherWarts; +import org.bukkit.material.PistonBaseMaterial; +import org.bukkit.material.PistonExtensionMaterial; +import org.bukkit.material.PoweredRail; +import org.bukkit.material.PressurePlate; +import org.bukkit.material.Pumpkin; +import org.bukkit.material.Rails; +import org.bukkit.material.RedstoneTorch; +import org.bukkit.material.RedstoneWire; +import org.bukkit.material.Sandstone; +import org.bukkit.material.Sapling; +import org.bukkit.material.Sign; +import org.bukkit.material.Skull; +import org.bukkit.material.SmoothBrick; +import org.bukkit.material.SpawnEgg; +import org.bukkit.material.Stairs; +import org.bukkit.material.Step; +import org.bukkit.material.Torch; +import org.bukkit.material.TrapDoor; +import org.bukkit.material.Tree; +import org.bukkit.material.Tripwire; +import org.bukkit.material.TripwireHook; +import org.bukkit.material.Vine; +import org.bukkit.material.Wood; +import org.bukkit.material.WoodenStep; +import org.bukkit.material.Wool; + +import com.google.common.collect.Maps; + +import org.bukkit.material.Banner; +import org.bukkit.material.Observer; + +import javax.annotation.Nullable; + +/** + * An enum of all material IDs accepted by the official server and client + */ +public enum Material { + AIR(0, 0), + STONE(1), + GRASS(2), + DIRT(3), + COBBLESTONE(4), + WOOD(5, Wood.class), + SAPLING(6, Sapling.class), + BEDROCK(7), + WATER(8, MaterialData.class), + STATIONARY_WATER(9, MaterialData.class), + LAVA(10, MaterialData.class), + STATIONARY_LAVA(11, MaterialData.class), + SAND(12), + GRAVEL(13), + GOLD_ORE(14), + IRON_ORE(15), + COAL_ORE(16), + LOG(17, Tree.class), + LEAVES(18, Leaves.class), + SPONGE(19), + GLASS(20), + LAPIS_ORE(21), + LAPIS_BLOCK(22), + DISPENSER(23, Dispenser.class), + SANDSTONE(24, Sandstone.class), + NOTE_BLOCK(25), + BED_BLOCK(26, Bed.class), + POWERED_RAIL(27, PoweredRail.class), + DETECTOR_RAIL(28, DetectorRail.class), + PISTON_STICKY_BASE(29, PistonBaseMaterial.class), + WEB(30), + LONG_GRASS(31, LongGrass.class), + DEAD_BUSH(32), + PISTON_BASE(33, PistonBaseMaterial.class), + PISTON_EXTENSION(34, PistonExtensionMaterial.class), + WOOL(35, Wool.class), + PISTON_MOVING_PIECE(36), + YELLOW_FLOWER(37), + RED_ROSE(38), + BROWN_MUSHROOM(39), + RED_MUSHROOM(40), + GOLD_BLOCK(41), + IRON_BLOCK(42), + DOUBLE_STEP(43, Step.class), + STEP(44, Step.class), + BRICK(45), + TNT(46), + BOOKSHELF(47), + MOSSY_COBBLESTONE(48), + OBSIDIAN(49), + TORCH(50, Torch.class), + FIRE(51), + MOB_SPAWNER(52), + WOOD_STAIRS(53, Stairs.class), + CHEST(54, Chest.class), + REDSTONE_WIRE(55, RedstoneWire.class), + DIAMOND_ORE(56), + DIAMOND_BLOCK(57), + WORKBENCH(58), + CROPS(59, Crops.class), + SOIL(60, MaterialData.class), + FURNACE(61, Furnace.class), + BURNING_FURNACE(62, Furnace.class), + SIGN_POST(63, 64, Sign.class), + WOODEN_DOOR(64, Door.class), + LADDER(65, Ladder.class), + RAILS(66, Rails.class), + COBBLESTONE_STAIRS(67, Stairs.class), + WALL_SIGN(68, 64, Sign.class), + LEVER(69, Lever.class), + STONE_PLATE(70, PressurePlate.class), + IRON_DOOR_BLOCK(71, Door.class), + WOOD_PLATE(72, PressurePlate.class), + REDSTONE_ORE(73), + GLOWING_REDSTONE_ORE(74), + REDSTONE_TORCH_OFF(75, RedstoneTorch.class), + REDSTONE_TORCH_ON(76, RedstoneTorch.class), + STONE_BUTTON(77, Button.class), + SNOW(78), + ICE(79), + SNOW_BLOCK(80), + CACTUS(81, MaterialData.class), + CLAY(82), + SUGAR_CANE_BLOCK(83, MaterialData.class), + JUKEBOX(84), + FENCE(85), + PUMPKIN(86, Pumpkin.class), + NETHERRACK(87), + SOUL_SAND(88), + GLOWSTONE(89), + PORTAL(90), + JACK_O_LANTERN(91, Pumpkin.class), + CAKE_BLOCK(92, 64, Cake.class), + DIODE_BLOCK_OFF(93, Diode.class), + DIODE_BLOCK_ON(94, Diode.class), + STAINED_GLASS(95), + TRAP_DOOR(96, TrapDoor.class), + MONSTER_EGGS(97, MonsterEggs.class), + SMOOTH_BRICK(98, SmoothBrick.class), + HUGE_MUSHROOM_1(99, Mushroom.class), + HUGE_MUSHROOM_2(100, Mushroom.class), + IRON_FENCE(101), + THIN_GLASS(102), + MELON_BLOCK(103), + PUMPKIN_STEM(104, MaterialData.class), + MELON_STEM(105, MaterialData.class), + VINE(106, Vine.class), + FENCE_GATE(107, Gate.class), + BRICK_STAIRS(108, Stairs.class), + SMOOTH_STAIRS(109, Stairs.class), + MYCEL(110), + WATER_LILY(111), + NETHER_BRICK(112), + NETHER_FENCE(113), + NETHER_BRICK_STAIRS(114, Stairs.class), + NETHER_WARTS(115, NetherWarts.class), + ENCHANTMENT_TABLE(116), + BREWING_STAND(117, MaterialData.class), + CAULDRON(118, Cauldron.class), + ENDER_PORTAL(119), + ENDER_PORTAL_FRAME(120), + ENDER_STONE(121), + DRAGON_EGG(122), + REDSTONE_LAMP_OFF(123), + REDSTONE_LAMP_ON(124), + WOOD_DOUBLE_STEP(125, Wood.class), + WOOD_STEP(126, WoodenStep.class), + COCOA(127, CocoaPlant.class), + SANDSTONE_STAIRS(128, Stairs.class), + EMERALD_ORE(129), + ENDER_CHEST(130, EnderChest.class), + TRIPWIRE_HOOK(131, TripwireHook.class), + TRIPWIRE(132, Tripwire.class), + EMERALD_BLOCK(133), + SPRUCE_WOOD_STAIRS(134, Stairs.class), + BIRCH_WOOD_STAIRS(135, Stairs.class), + JUNGLE_WOOD_STAIRS(136, Stairs.class), + COMMAND(137, Command.class), + BEACON(138), + COBBLE_WALL(139), + FLOWER_POT(140, FlowerPot.class), + CARROT(141, Crops.class), + POTATO(142, Crops.class), + WOOD_BUTTON(143, Button.class), + SKULL(144, Skull.class), + ANVIL(145), + TRAPPED_CHEST(146, Chest.class), + GOLD_PLATE(147), + IRON_PLATE(148), + REDSTONE_COMPARATOR_OFF(149, Comparator.class), + REDSTONE_COMPARATOR_ON(150, Comparator.class), + DAYLIGHT_DETECTOR(151), + REDSTONE_BLOCK(152), + QUARTZ_ORE(153), + HOPPER(154, Hopper.class), + QUARTZ_BLOCK(155), + QUARTZ_STAIRS(156, Stairs.class), + ACTIVATOR_RAIL(157, PoweredRail.class), + DROPPER(158, Dispenser.class), + STAINED_CLAY(159), + STAINED_GLASS_PANE(160), + LEAVES_2(161, Leaves.class), + LOG_2(162, Tree.class), + ACACIA_STAIRS(163, Stairs.class), + DARK_OAK_STAIRS(164, Stairs.class), + SLIME_BLOCK(165), + BARRIER(166), + IRON_TRAPDOOR(167, TrapDoor.class), + PRISMARINE(168), + SEA_LANTERN(169), + HAY_BLOCK(170), + CARPET(171), + HARD_CLAY(172), + COAL_BLOCK(173), + PACKED_ICE(174), + DOUBLE_PLANT(175), + STANDING_BANNER(176, Banner.class), + WALL_BANNER(177, Banner.class), + DAYLIGHT_DETECTOR_INVERTED(178), + RED_SANDSTONE(179), + RED_SANDSTONE_STAIRS(180, Stairs.class), + DOUBLE_STONE_SLAB2(181), + STONE_SLAB2(182), + SPRUCE_FENCE_GATE(183, Gate.class), + BIRCH_FENCE_GATE(184, Gate.class), + JUNGLE_FENCE_GATE(185, Gate.class), + DARK_OAK_FENCE_GATE(186, Gate.class), + ACACIA_FENCE_GATE(187, Gate.class), + SPRUCE_FENCE(188), + BIRCH_FENCE(189), + JUNGLE_FENCE(190), + DARK_OAK_FENCE(191), + ACACIA_FENCE(192), + SPRUCE_DOOR(193, Door.class), + BIRCH_DOOR(194, Door.class), + JUNGLE_DOOR(195, Door.class), + ACACIA_DOOR(196, Door.class), + DARK_OAK_DOOR(197, Door.class), + END_ROD(198), + CHORUS_PLANT(199), + CHORUS_FLOWER(200), + PURPUR_BLOCK(201), + PURPUR_PILLAR(202), + PURPUR_STAIRS(203, Stairs.class), + PURPUR_DOUBLE_SLAB(204), + PURPUR_SLAB(205), + END_BRICKS(206), + BEETROOT_BLOCK(207, Crops.class), + GRASS_PATH(208), + END_GATEWAY(209), + COMMAND_REPEATING(210, Command.class), + COMMAND_CHAIN(211, Command.class), + FROSTED_ICE(212), + MAGMA(213), + NETHER_WART_BLOCK(214), + RED_NETHER_BRICK(215), + BONE_BLOCK(216), + STRUCTURE_VOID(217), + OBSERVER(218, Observer.class), + WHITE_SHULKER_BOX(219, 1), + ORANGE_SHULKER_BOX(220, 1), + MAGENTA_SHULKER_BOX(221, 1), + LIGHT_BLUE_SHULKER_BOX(222, 1), + YELLOW_SHULKER_BOX(223, 1), + LIME_SHULKER_BOX(224, 1), + PINK_SHULKER_BOX(225, 1), + GRAY_SHULKER_BOX(226, 1), + SILVER_SHULKER_BOX(227, 1), + CYAN_SHULKER_BOX(228, 1), + PURPLE_SHULKER_BOX(229, 1), + BLUE_SHULKER_BOX(230, 1), + BROWN_SHULKER_BOX(231, 1), + GREEN_SHULKER_BOX(232, 1), + RED_SHULKER_BOX(233, 1), + BLACK_SHULKER_BOX(234, 1), + WHITE_GLAZED_TERRACOTTA(235), + ORANGE_GLAZED_TERRACOTTA(236), + MAGENTA_GLAZED_TERRACOTTA(237), + LIGHT_BLUE_GLAZED_TERRACOTTA(238), + YELLOW_GLAZED_TERRACOTTA(239), + LIME_GLAZED_TERRACOTTA(240), + PINK_GLAZED_TERRACOTTA(241), + GRAY_GLAZED_TERRACOTTA(242), + SILVER_GLAZED_TERRACOTTA(243), + CYAN_GLAZED_TERRACOTTA(244), + PURPLE_GLAZED_TERRACOTTA(245), + BLUE_GLAZED_TERRACOTTA(246), + BROWN_GLAZED_TERRACOTTA(247), + GREEN_GLAZED_TERRACOTTA(248), + RED_GLAZED_TERRACOTTA(249), + BLACK_GLAZED_TERRACOTTA(250), + CONCRETE(251), + CONCRETE_POWDER(252), + STRUCTURE_BLOCK(255), + // ----- Item Separator ----- + IRON_SPADE(256, 1, 250), + IRON_PICKAXE(257, 1, 250), + IRON_AXE(258, 1, 250), + FLINT_AND_STEEL(259, 1, 64), + APPLE(260), + BOW(261, 1, 384), + ARROW(262), + COAL(263, Coal.class), + DIAMOND(264), + IRON_INGOT(265), + GOLD_INGOT(266), + IRON_SWORD(267, 1, 250), + WOOD_SWORD(268, 1, 59), + WOOD_SPADE(269, 1, 59), + WOOD_PICKAXE(270, 1, 59), + WOOD_AXE(271, 1, 59), + STONE_SWORD(272, 1, 131), + STONE_SPADE(273, 1, 131), + STONE_PICKAXE(274, 1, 131), + STONE_AXE(275, 1, 131), + DIAMOND_SWORD(276, 1, 1561), + DIAMOND_SPADE(277, 1, 1561), + DIAMOND_PICKAXE(278, 1, 1561), + DIAMOND_AXE(279, 1, 1561), + STICK(280), + BOWL(281), + MUSHROOM_SOUP(282, 1), + GOLD_SWORD(283, 1, 32), + GOLD_SPADE(284, 1, 32), + GOLD_PICKAXE(285, 1, 32), + GOLD_AXE(286, 1, 32), + STRING(287), + FEATHER(288), + SULPHUR(289), + WOOD_HOE(290, 1, 59), + STONE_HOE(291, 1, 131), + IRON_HOE(292, 1, 250), + DIAMOND_HOE(293, 1, 1561), + GOLD_HOE(294, 1, 32), + SEEDS(295), + WHEAT(296), + BREAD(297), + LEATHER_HELMET(298, 1, 55), + LEATHER_CHESTPLATE(299, 1, 80), + LEATHER_LEGGINGS(300, 1, 75), + LEATHER_BOOTS(301, 1, 65), + CHAINMAIL_HELMET(302, 1, 165), + CHAINMAIL_CHESTPLATE(303, 1, 240), + CHAINMAIL_LEGGINGS(304, 1, 225), + CHAINMAIL_BOOTS(305, 1, 195), + IRON_HELMET(306, 1, 165), + IRON_CHESTPLATE(307, 1, 240), + IRON_LEGGINGS(308, 1, 225), + IRON_BOOTS(309, 1, 195), + DIAMOND_HELMET(310, 1, 363), + DIAMOND_CHESTPLATE(311, 1, 528), + DIAMOND_LEGGINGS(312, 1, 495), + DIAMOND_BOOTS(313, 1, 429), + GOLD_HELMET(314, 1, 77), + GOLD_CHESTPLATE(315, 1, 112), + GOLD_LEGGINGS(316, 1, 105), + GOLD_BOOTS(317, 1, 91), + FLINT(318), + PORK(319), + GRILLED_PORK(320), + PAINTING(321), + GOLDEN_APPLE(322), + SIGN(323, 16), + WOOD_DOOR(324, 64), + BUCKET(325, 16), + WATER_BUCKET(326, 1), + LAVA_BUCKET(327, 1), + MINECART(328, 1), + SADDLE(329, 1), + IRON_DOOR(330, 64), + REDSTONE(331), + SNOW_BALL(332, 16), + BOAT(333, 1), + LEATHER(334), + MILK_BUCKET(335, 1), + CLAY_BRICK(336), + CLAY_BALL(337), + SUGAR_CANE(338), + PAPER(339), + BOOK(340), + SLIME_BALL(341), + STORAGE_MINECART(342, 1), + POWERED_MINECART(343, 1), + EGG(344, 16), + COMPASS(345), + FISHING_ROD(346, 1, 64), + WATCH(347), + GLOWSTONE_DUST(348), + RAW_FISH(349), + COOKED_FISH(350), + INK_SACK(351, Dye.class), + BONE(352), + SUGAR(353), + CAKE(354, 1), + BED(355, 1), + DIODE(356), + COOKIE(357), + /** + * @see MapView + */ + MAP(358, MaterialData.class), + SHEARS(359, 1, 238), + MELON(360), + PUMPKIN_SEEDS(361), + MELON_SEEDS(362), + RAW_BEEF(363), + COOKED_BEEF(364), + RAW_CHICKEN(365), + COOKED_CHICKEN(366), + ROTTEN_FLESH(367), + ENDER_PEARL(368, 16), + BLAZE_ROD(369), + GHAST_TEAR(370), + GOLD_NUGGET(371), + NETHER_STALK(372), + POTION(373, 1, MaterialData.class), + GLASS_BOTTLE(374), + SPIDER_EYE(375), + FERMENTED_SPIDER_EYE(376), + BLAZE_POWDER(377), + MAGMA_CREAM(378), + BREWING_STAND_ITEM(379), + CAULDRON_ITEM(380), + EYE_OF_ENDER(381), + SPECKLED_MELON(382), + MONSTER_EGG(383, 64, SpawnEgg.class), + EXP_BOTTLE(384, 64), + FIREBALL(385, 64), + BOOK_AND_QUILL(386, 1), + WRITTEN_BOOK(387, 16), + EMERALD(388, 64), + ITEM_FRAME(389), + FLOWER_POT_ITEM(390), + CARROT_ITEM(391), + POTATO_ITEM(392), + BAKED_POTATO(393), + POISONOUS_POTATO(394), + EMPTY_MAP(395), + GOLDEN_CARROT(396), + SKULL_ITEM(397), + CARROT_STICK(398, 1, 25), + NETHER_STAR(399), + PUMPKIN_PIE(400), + FIREWORK(401), + FIREWORK_CHARGE(402), + ENCHANTED_BOOK(403, 1), + REDSTONE_COMPARATOR(404), + NETHER_BRICK_ITEM(405), + QUARTZ(406), + EXPLOSIVE_MINECART(407, 1), + HOPPER_MINECART(408, 1), + PRISMARINE_SHARD(409), + PRISMARINE_CRYSTALS(410), + RABBIT(411), + COOKED_RABBIT(412), + RABBIT_STEW(413, 1), + RABBIT_FOOT(414), + RABBIT_HIDE(415), + ARMOR_STAND(416, 16), + IRON_BARDING(417, 1), + GOLD_BARDING(418, 1), + DIAMOND_BARDING(419, 1), + LEASH(420), + NAME_TAG(421), + COMMAND_MINECART(422, 1), + MUTTON(423), + COOKED_MUTTON(424), + BANNER(425, 16), + END_CRYSTAL(426), + SPRUCE_DOOR_ITEM(427), + BIRCH_DOOR_ITEM(428), + JUNGLE_DOOR_ITEM(429), + ACACIA_DOOR_ITEM(430), + DARK_OAK_DOOR_ITEM(431), + CHORUS_FRUIT(432), + CHORUS_FRUIT_POPPED(433), + BEETROOT(434), + BEETROOT_SEEDS(435), + BEETROOT_SOUP(436, 1), + DRAGONS_BREATH(437), + SPLASH_POTION(438, 1), + SPECTRAL_ARROW(439), + TIPPED_ARROW(440), + LINGERING_POTION(441, 1), + SHIELD(442, 1, 336), + ELYTRA(443, 1, 431), + BOAT_SPRUCE(444, 1), + BOAT_BIRCH(445, 1), + BOAT_JUNGLE(446, 1), + BOAT_ACACIA(447, 1), + BOAT_DARK_OAK(448, 1), + TOTEM(449, 1), + SHULKER_SHELL(450), + IRON_NUGGET(452), + KNOWLEDGE_BOOK(453, 1), + GOLD_RECORD(2256, 1), + GREEN_RECORD(2257, 1), + RECORD_3(2258, 1), + RECORD_4(2259, 1), + RECORD_5(2260, 1), + RECORD_6(2261, 1), + RECORD_7(2262, 1), + RECORD_8(2263, 1), + RECORD_9(2264, 1), + RECORD_10(2265, 1), + RECORD_11(2266, 1), + RECORD_12(2267, 1), + ; + + private final int id; + private final Constructor ctor; + private static Material[] byId = new Material[38000]; + private static Material[] byBlockId = new Material[10000]; //temp w + private final static Map BY_NAME = Maps.newHashMap(); + private final int maxStack; + private final short durability; + + private Material(final int id) { + this(id, 64); + } + + private Material(final int id, final int stack) { + this(id, stack, MaterialData.class); + } + + private Material(final int id, final int stack, final int durability) { + this(id, stack, durability, MaterialData.class); + } + + private Material(final int id, final Class data) { + this(id, 64, data); + } + + private Material(final int id, final int stack, final Class data) { + this(id, stack, 0, data); + } + + private Material(final int id, final int stack, final int durability, final Class data) { + this.id = id; + this.durability = (short) durability; + this.maxStack = stack; + // try to cache the constructor for this material + try { + this.ctor = data.getConstructor(int.class, byte.class); + } catch (NoSuchMethodException ex) { + throw new AssertionError(ex); + } catch (SecurityException ex) { + throw new AssertionError(ex); + } + } + + /** + * Gets the item ID or block ID of this Material + * + * @return ID of this material + * @deprecated Magic value + */ + + public int getId() { + return id; + } + + /** + * Gets the maximum amount of this material that can be held in a stack + * + * @return Maximum stack size for this material + */ + public int getMaxStackSize() { + return maxStack; + } + + /** + * Gets the maximum durability of this material + * + * @return Maximum durability for this material + */ + public short getMaxDurability() { + return durability; + } + + /** + * Gets the MaterialData class associated with this Material + * + * @return MaterialData associated with this Material + */ + public Class getData() { + return ctor.getDeclaringClass(); + } + + /** + * Constructs a new MaterialData relevant for this Material, with the + * given initial data + * + * @param raw Initial data to construct the MaterialData with + * @return New MaterialData with the given data + * @deprecated Magic value + */ + + public MaterialData getNewData(final byte raw) { + try { + return ctor.newInstance(id, raw); + } catch (InstantiationException ex) { + final Throwable t = ex.getCause(); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof Error) { + throw (Error) t; + } + throw new AssertionError(t); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + /** + * Checks if this Material is a placable block + * + * @return true if this material is a block + */ + public boolean isBlock() { + for (Material material : byBlockId) { + if (this == material) return true; + } + return false; + } + + /** + * Checks if this Material is edible. + * + * @return true if this Material is edible. + */ + public boolean isEdible() { + switch (this) { + case BREAD: + case CARROT_ITEM: + case BAKED_POTATO: + case POTATO_ITEM: + case POISONOUS_POTATO: + case GOLDEN_CARROT: + case PUMPKIN_PIE: + case COOKIE: + case MELON: + case MUSHROOM_SOUP: + case RAW_CHICKEN: + case COOKED_CHICKEN: + case RAW_BEEF: + case COOKED_BEEF: + case RAW_FISH: + case COOKED_FISH: + case PORK: + case GRILLED_PORK: + case APPLE: + case GOLDEN_APPLE: + case ROTTEN_FLESH: + case SPIDER_EYE: + case RABBIT: + case COOKED_RABBIT: + case RABBIT_STEW: + case MUTTON: + case COOKED_MUTTON: + case BEETROOT: + case CHORUS_FRUIT: + case BEETROOT_SOUP: + return true; + default: + return false; + } + } + + /** + * Attempts to get the Material with the given ID + * + * @param id ID of the material to get + * @return Material if found, or null + * @deprecated Magic value + */ + + public static Material getMaterial(final int id) { + if (byId.length > id && id >= 0) { + return byId[id]; + } else { + return null; + } + } + + /** + * Attempts to get the Material with the given name. + *

+ * This is a normal lookup, names must be the precise name they are given + * in the enum. + * + * @param name Name of the material to get + * @return Material if found, or null + */ + public static Material getMaterial(final String name) { + return BY_NAME.get(name); + } + + /** + * Attempts to match the Material with the given name. + *

+ * This is a match lookup; names will be converted to uppercase, then + * stripped of special characters in an attempt to format it like the + * enum. + *

+ * Using this for match by ID is deprecated. + * + * @param name Name of the material to get + * @return Material if found, or null + */ + public static Material matchMaterial(final String name) { + Validate.notNull(name, "Name cannot be null"); + + Material result = null; + + try { + result = getMaterial(Integer.parseInt(name)); + } catch (NumberFormatException ex) {} + + if (result == null) { + String filtered = name.toUpperCase(java.util.Locale.ENGLISH); + + filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", ""); + result = BY_NAME.get(filtered); + } + + return result; + } + + @Nullable + public static Material addMaterial(Material material) { + if (byId[material.id] == null) { + byId[material.id] = material; + BY_NAME.put(material.name().toUpperCase().replaceAll("(:|\\s)", "_").replaceAll("\\W", ""), material); + BY_NAME.put("X" + String.valueOf(material.id), material); + return material; + } + return null; + } + + @Nullable + public static Material addBlockMaterial(Material Blockmaterial) { + if (byBlockId[Blockmaterial.id] == null) { + byBlockId[Blockmaterial.id] = Blockmaterial; + return Blockmaterial; + } + return null; + } + + public static Material getBlockMaterial(final int id) { + if (byBlockId.length > id && id >= 0) { + return byBlockId[id]; + } else { + return null; + } + } + + static { + for (Material material : values()) { + if (byId.length > material.id) { + byId[material.id] = material; + } else { + byId = Arrays.copyOfRange(byId, 0, material.id + 2); + byId[material.id] = material; + } + BY_NAME.put(material.name(), material); + } + } + + /** + * @return True if this material represents a playable music disk. + */ + public boolean isRecord() { + return id >= GOLD_RECORD.id && id <= RECORD_12.id; + } + + /** + * Check if the material is a block and solid (can be built upon) + * + * @return True if this material is a block and solid + */ + public boolean isSolid() { + if (!isBlock() || id == 0) { + return false; + } + switch (this) { + case STONE: + case GRASS: + case DIRT: + case COBBLESTONE: + case WOOD: + case BEDROCK: + case SAND: + case GRAVEL: + case GOLD_ORE: + case IRON_ORE: + case COAL_ORE: + case LOG: + case LEAVES: + case SPONGE: + case GLASS: + case LAPIS_ORE: + case LAPIS_BLOCK: + case DISPENSER: + case SANDSTONE: + case NOTE_BLOCK: + case BED_BLOCK: + case PISTON_STICKY_BASE: + case PISTON_BASE: + case PISTON_EXTENSION: + case WOOL: + case PISTON_MOVING_PIECE: + case GOLD_BLOCK: + case IRON_BLOCK: + case DOUBLE_STEP: + case STEP: + case BRICK: + case TNT: + case BOOKSHELF: + case MOSSY_COBBLESTONE: + case OBSIDIAN: + case MOB_SPAWNER: + case WOOD_STAIRS: + case CHEST: + case DIAMOND_ORE: + case DIAMOND_BLOCK: + case WORKBENCH: + case SOIL: + case FURNACE: + case BURNING_FURNACE: + case SIGN_POST: + case WOODEN_DOOR: + case COBBLESTONE_STAIRS: + case WALL_SIGN: + case STONE_PLATE: + case IRON_DOOR_BLOCK: + case WOOD_PLATE: + case REDSTONE_ORE: + case GLOWING_REDSTONE_ORE: + case ICE: + case SNOW_BLOCK: + case CACTUS: + case CLAY: + case JUKEBOX: + case FENCE: + case PUMPKIN: + case NETHERRACK: + case SOUL_SAND: + case GLOWSTONE: + case JACK_O_LANTERN: + case CAKE_BLOCK: + case STAINED_GLASS: + case TRAP_DOOR: + case MONSTER_EGGS: + case SMOOTH_BRICK: + case HUGE_MUSHROOM_1: + case HUGE_MUSHROOM_2: + case IRON_FENCE: + case THIN_GLASS: + case MELON_BLOCK: + case FENCE_GATE: + case BRICK_STAIRS: + case SMOOTH_STAIRS: + case MYCEL: + case NETHER_BRICK: + case NETHER_FENCE: + case NETHER_BRICK_STAIRS: + case ENCHANTMENT_TABLE: + case BREWING_STAND: + case CAULDRON: + case ENDER_PORTAL_FRAME: + case ENDER_STONE: + case DRAGON_EGG: + case REDSTONE_LAMP_OFF: + case REDSTONE_LAMP_ON: + case WOOD_DOUBLE_STEP: + case WOOD_STEP: + case SANDSTONE_STAIRS: + case EMERALD_ORE: + case ENDER_CHEST: + case EMERALD_BLOCK: + case SPRUCE_WOOD_STAIRS: + case BIRCH_WOOD_STAIRS: + case JUNGLE_WOOD_STAIRS: + case COMMAND: + case BEACON: + case COBBLE_WALL: + case ANVIL: + case TRAPPED_CHEST: + case GOLD_PLATE: + case IRON_PLATE: + case DAYLIGHT_DETECTOR: + case REDSTONE_BLOCK: + case QUARTZ_ORE: + case HOPPER: + case QUARTZ_BLOCK: + case QUARTZ_STAIRS: + case DROPPER: + case STAINED_CLAY: + case HAY_BLOCK: + case HARD_CLAY: + case COAL_BLOCK: + case STAINED_GLASS_PANE: + case LEAVES_2: + case LOG_2: + case ACACIA_STAIRS: + case DARK_OAK_STAIRS: + case PACKED_ICE: + case RED_SANDSTONE: + case SLIME_BLOCK: + case BARRIER: + case IRON_TRAPDOOR: + case PRISMARINE: + case SEA_LANTERN: + case DOUBLE_STONE_SLAB2: + case RED_SANDSTONE_STAIRS: + case STONE_SLAB2: + case SPRUCE_FENCE_GATE: + case BIRCH_FENCE_GATE: + case JUNGLE_FENCE_GATE: + case DARK_OAK_FENCE_GATE: + case ACACIA_FENCE_GATE: + case SPRUCE_FENCE: + case BIRCH_FENCE: + case JUNGLE_FENCE: + case DARK_OAK_FENCE: + case ACACIA_FENCE: + case STANDING_BANNER: + case WALL_BANNER: + case DAYLIGHT_DETECTOR_INVERTED: + case SPRUCE_DOOR: + case BIRCH_DOOR: + case JUNGLE_DOOR: + case ACACIA_DOOR: + case DARK_OAK_DOOR: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_STAIRS: + case PURPUR_DOUBLE_SLAB: + case PURPUR_SLAB: + case END_BRICKS: + case GRASS_PATH: + case STRUCTURE_BLOCK: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + case FROSTED_ICE: + case MAGMA: + case NETHER_WART_BLOCK: + case RED_NETHER_BRICK: + case BONE_BLOCK: + case OBSERVER: + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + case WHITE_GLAZED_TERRACOTTA: + case ORANGE_GLAZED_TERRACOTTA: + case MAGENTA_GLAZED_TERRACOTTA: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case YELLOW_GLAZED_TERRACOTTA: + case LIME_GLAZED_TERRACOTTA: + case PINK_GLAZED_TERRACOTTA: + case GRAY_GLAZED_TERRACOTTA: + case SILVER_GLAZED_TERRACOTTA: + case CYAN_GLAZED_TERRACOTTA: + case PURPLE_GLAZED_TERRACOTTA: + case BLUE_GLAZED_TERRACOTTA: + case BROWN_GLAZED_TERRACOTTA: + case GREEN_GLAZED_TERRACOTTA: + case RED_GLAZED_TERRACOTTA: + case BLACK_GLAZED_TERRACOTTA: + case CONCRETE: + case CONCRETE_POWDER: + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and does not block any light + * + * @return True if this material is a block and does not block any light + */ + public boolean isTransparent() { + if (!isBlock()) { + return false; + } + switch (this) { + case AIR: + case SAPLING: + case POWERED_RAIL: + case DETECTOR_RAIL: + case LONG_GRASS: + case DEAD_BUSH: + case YELLOW_FLOWER: + case RED_ROSE: + case BROWN_MUSHROOM: + case RED_MUSHROOM: + case TORCH: + case FIRE: + case REDSTONE_WIRE: + case CROPS: + case LADDER: + case RAILS: + case LEVER: + case REDSTONE_TORCH_OFF: + case REDSTONE_TORCH_ON: + case STONE_BUTTON: + case SNOW: + case SUGAR_CANE_BLOCK: + case PORTAL: + case DIODE_BLOCK_OFF: + case DIODE_BLOCK_ON: + case PUMPKIN_STEM: + case MELON_STEM: + case VINE: + case WATER_LILY: + case NETHER_WARTS: + case ENDER_PORTAL: + case COCOA: + case TRIPWIRE_HOOK: + case TRIPWIRE: + case FLOWER_POT: + case CARROT: + case POTATO: + case WOOD_BUTTON: + case SKULL: + case REDSTONE_COMPARATOR_OFF: + case REDSTONE_COMPARATOR_ON: + case ACTIVATOR_RAIL: + case CARPET: + case DOUBLE_PLANT: + case END_ROD: + case CHORUS_PLANT: + case CHORUS_FLOWER: + case BEETROOT_BLOCK: + case END_GATEWAY: + case STRUCTURE_VOID: + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and can catch fire + * + * @return True if this material is a block and can catch fire + */ + public boolean isFlammable() { + if (!isBlock()) { + return false; + } + switch (this) { + case WOOD: + case LOG: + case LEAVES: + case NOTE_BLOCK: + case BED_BLOCK: + case LONG_GRASS: + case DEAD_BUSH: + case WOOL: + case TNT: + case BOOKSHELF: + case WOOD_STAIRS: + case CHEST: + case WORKBENCH: + case SIGN_POST: + case WOODEN_DOOR: + case WALL_SIGN: + case WOOD_PLATE: + case JUKEBOX: + case FENCE: + case TRAP_DOOR: + case HUGE_MUSHROOM_1: + case HUGE_MUSHROOM_2: + case VINE: + case FENCE_GATE: + case WOOD_DOUBLE_STEP: + case WOOD_STEP: + case SPRUCE_WOOD_STAIRS: + case BIRCH_WOOD_STAIRS: + case JUNGLE_WOOD_STAIRS: + case TRAPPED_CHEST: + case DAYLIGHT_DETECTOR: + case CARPET: + case LEAVES_2: + case LOG_2: + case ACACIA_STAIRS: + case DARK_OAK_STAIRS: + case DOUBLE_PLANT: + case SPRUCE_FENCE_GATE: + case BIRCH_FENCE_GATE: + case JUNGLE_FENCE_GATE: + case DARK_OAK_FENCE_GATE: + case ACACIA_FENCE_GATE: + case SPRUCE_FENCE: + case BIRCH_FENCE: + case JUNGLE_FENCE: + case DARK_OAK_FENCE: + case ACACIA_FENCE: + case STANDING_BANNER: + case WALL_BANNER: + case DAYLIGHT_DETECTOR_INVERTED: + case SPRUCE_DOOR: + case BIRCH_DOOR: + case JUNGLE_DOOR: + case ACACIA_DOOR: + case DARK_OAK_DOOR: + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and can burn away + * + * @return True if this material is a block and can burn away + */ + public boolean isBurnable() { + if (!isBlock()) { + return false; + } + switch (this) { + case WOOD: + case LOG: + case LEAVES: + case LONG_GRASS: + case WOOL: + case YELLOW_FLOWER: + case RED_ROSE: + case TNT: + case BOOKSHELF: + case WOOD_STAIRS: + case FENCE: + case VINE: + case WOOD_DOUBLE_STEP: + case WOOD_STEP: + case SPRUCE_WOOD_STAIRS: + case BIRCH_WOOD_STAIRS: + case JUNGLE_WOOD_STAIRS: + case HAY_BLOCK: + case COAL_BLOCK: + case LEAVES_2: + case LOG_2: + case CARPET: + case DOUBLE_PLANT: + case DEAD_BUSH: + case FENCE_GATE: + case SPRUCE_FENCE_GATE: + case BIRCH_FENCE_GATE: + case JUNGLE_FENCE_GATE: + case DARK_OAK_FENCE_GATE: + case ACACIA_FENCE_GATE: + case SPRUCE_FENCE: + case BIRCH_FENCE: + case JUNGLE_FENCE: + case DARK_OAK_FENCE: + case ACACIA_FENCE: + case ACACIA_STAIRS: + case DARK_OAK_STAIRS: + return true; + default: + return false; + } + } + + /** + * Checks if this Material can be used as fuel in a Furnace + * + * @return true if this Material can be used as fuel. + */ + public boolean isFuel() { + switch (this) { + case LAVA_BUCKET: + case COAL_BLOCK: + case BLAZE_ROD: + case COAL: + case BOAT: + case BOAT_ACACIA: + case BOAT_BIRCH: + case BOAT_DARK_OAK: + case BOAT_JUNGLE: + case BOAT_SPRUCE: + case LOG: + case LOG_2: + case WOOD: + case WOOD_PLATE: + case FENCE: + case ACACIA_FENCE: + case BIRCH_FENCE: + case DARK_OAK_FENCE: + case JUNGLE_FENCE: + case SPRUCE_FENCE: + case FENCE_GATE: + case ACACIA_FENCE_GATE: + case BIRCH_FENCE_GATE: + case DARK_OAK_FENCE_GATE: + case JUNGLE_FENCE_GATE: + case SPRUCE_FENCE_GATE: + case WOOD_STAIRS: + case ACACIA_STAIRS: + case BIRCH_WOOD_STAIRS: + case DARK_OAK_STAIRS: + case JUNGLE_WOOD_STAIRS: + case SPRUCE_WOOD_STAIRS: + case TRAP_DOOR: + case WORKBENCH: + case BOOKSHELF: + case CHEST: + case TRAPPED_CHEST: + case DAYLIGHT_DETECTOR: + case JUKEBOX: + case NOTE_BLOCK: + case HUGE_MUSHROOM_1: + case HUGE_MUSHROOM_2: + case BANNER: + case FISHING_ROD: + case LADDER: + case WOOD_SWORD: + case WOOD_PICKAXE: + case WOOD_AXE: + case WOOD_SPADE: + case WOOD_HOE: + case BOW: + case SIGN: + case WOOD_DOOR: + case ACACIA_DOOR_ITEM: + case BIRCH_DOOR_ITEM: + case DARK_OAK_DOOR_ITEM: + case JUNGLE_DOOR_ITEM: + case SPRUCE_DOOR_ITEM: + case WOOD_STEP: + case SAPLING: + case BOWL: + case STICK: + case WOOD_BUTTON: + case WOOL: + case CARPET: + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and completely blocks vision + * + * @return True if this material is a block and completely blocks vision + */ + public boolean isOccluding() { + if (!isBlock()) { + return false; + } + switch (this) { + case STONE: + case GRASS: + case DIRT: + case COBBLESTONE: + case WOOD: + case BEDROCK: + case SAND: + case GRAVEL: + case GOLD_ORE: + case IRON_ORE: + case COAL_ORE: + case LOG: + case SPONGE: + case LAPIS_ORE: + case LAPIS_BLOCK: + case DISPENSER: + case SANDSTONE: + case NOTE_BLOCK: + case WOOL: + case GOLD_BLOCK: + case IRON_BLOCK: + case DOUBLE_STEP: + case BRICK: + case BOOKSHELF: + case MOSSY_COBBLESTONE: + case OBSIDIAN: + case MOB_SPAWNER: + case DIAMOND_ORE: + case DIAMOND_BLOCK: + case WORKBENCH: + case FURNACE: + case BURNING_FURNACE: + case REDSTONE_ORE: + case GLOWING_REDSTONE_ORE: + case SNOW_BLOCK: + case CLAY: + case JUKEBOX: + case PUMPKIN: + case NETHERRACK: + case SOUL_SAND: + case JACK_O_LANTERN: + case MONSTER_EGGS: + case SMOOTH_BRICK: + case HUGE_MUSHROOM_1: + case HUGE_MUSHROOM_2: + case MELON_BLOCK: + case MYCEL: + case NETHER_BRICK: + case ENDER_STONE: + case REDSTONE_LAMP_OFF: + case REDSTONE_LAMP_ON: + case WOOD_DOUBLE_STEP: + case EMERALD_ORE: + case EMERALD_BLOCK: + case COMMAND: + case QUARTZ_ORE: + case QUARTZ_BLOCK: + case DROPPER: + case STAINED_CLAY: + case HAY_BLOCK: + case HARD_CLAY: + case COAL_BLOCK: + case LOG_2: + case PACKED_ICE: + case SLIME_BLOCK: + case BARRIER: + case PRISMARINE: + case RED_SANDSTONE: + case DOUBLE_STONE_SLAB2: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_DOUBLE_SLAB: + case END_BRICKS: + case STRUCTURE_BLOCK: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + case MAGMA: + case NETHER_WART_BLOCK: + case RED_NETHER_BRICK: + case BONE_BLOCK: + case WHITE_GLAZED_TERRACOTTA: + case ORANGE_GLAZED_TERRACOTTA: + case MAGENTA_GLAZED_TERRACOTTA: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case YELLOW_GLAZED_TERRACOTTA: + case LIME_GLAZED_TERRACOTTA: + case PINK_GLAZED_TERRACOTTA: + case GRAY_GLAZED_TERRACOTTA: + case SILVER_GLAZED_TERRACOTTA: + case CYAN_GLAZED_TERRACOTTA: + case PURPLE_GLAZED_TERRACOTTA: + case BLUE_GLAZED_TERRACOTTA: + case BROWN_GLAZED_TERRACOTTA: + case GREEN_GLAZED_TERRACOTTA: + case RED_GLAZED_TERRACOTTA: + case BLACK_GLAZED_TERRACOTTA: + case CONCRETE: + case CONCRETE_POWDER: + return true; + default: + return false; + } + } + + /** + * @return True if this material is affected by gravity. + */ + public boolean hasGravity() { + if (!isBlock()) { + return false; + } + switch (this) { + case SAND: + case GRAVEL: + case ANVIL: + case CONCRETE_POWDER: + return true; + default: + return false; + } + } + + /** + * Checks if this Material is an obtainable item. + * + * @return true if this material is an item + */ + public boolean isItem() { + switch (this) { + case ACACIA_DOOR: + case BED_BLOCK: + case BEETROOT_BLOCK: + case BIRCH_DOOR: + case BREWING_STAND: + case BURNING_FURNACE: + case CAKE_BLOCK: + case CARROT: + case CAULDRON: + case COCOA: + case CROPS: + case DARK_OAK_DOOR: + case DAYLIGHT_DETECTOR_INVERTED: + case DIODE_BLOCK_OFF: + case DIODE_BLOCK_ON: + case DOUBLE_STEP: + case DOUBLE_STONE_SLAB2: + case ENDER_PORTAL: + case END_GATEWAY: + case FIRE: + case FLOWER_POT: + case FROSTED_ICE: + case GLOWING_REDSTONE_ORE: + case IRON_DOOR_BLOCK: + case JUNGLE_DOOR: + case LAVA: + case MELON_STEM: + case NETHER_WARTS: + case PISTON_EXTENSION: + case PISTON_MOVING_PIECE: + case PORTAL: + case POTATO: + case PUMPKIN_STEM: + case PURPUR_DOUBLE_SLAB: + case REDSTONE_COMPARATOR_OFF: + case REDSTONE_COMPARATOR_ON: + case REDSTONE_LAMP_ON: + case REDSTONE_TORCH_OFF: + case REDSTONE_WIRE: + case SIGN_POST: + case SKULL: + case SPRUCE_DOOR: + case STANDING_BANNER: + case STATIONARY_LAVA: + case STATIONARY_WATER: + case SUGAR_CANE_BLOCK: + case TRIPWIRE: + case WALL_BANNER: + case WALL_SIGN: + case WATER: + case WOODEN_DOOR: + case WOOD_DOUBLE_STEP: + return false; + default: + return true; + } + } +} diff --git a/src/main/java/org/bukkit/Nameable.java b/src/main/java/org/bukkit/Nameable.java new file mode 100644 index 00000000..49cf519c --- /dev/null +++ b/src/main/java/org/bukkit/Nameable.java @@ -0,0 +1,28 @@ +package org.bukkit; + +public interface Nameable { + + /** + * Gets the custom name on a mob or block. If there is no name this method + * will return null. + *

+ * This value has no effect on players, they will always use their real + * name. + * + * @return name of the mob/block or null + */ + public String getCustomName(); + + /** + * Sets a custom name on a mob or block. This name will be used in death + * messages and can be sent to the client as a nameplate over the mob. + *

+ * Setting the name to null or an empty string will clear it. + *

+ * This value has no effect on players, they will always use their real + * name. + * + * @param name the name to set + */ + public void setCustomName(String name); +} diff --git a/src/main/java/org/bukkit/NamespacedKey.java b/src/main/java/org/bukkit/NamespacedKey.java new file mode 100644 index 00000000..3231c954 --- /dev/null +++ b/src/main/java/org/bukkit/NamespacedKey.java @@ -0,0 +1,131 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; +import java.util.Locale; +import java.util.UUID; +import java.util.regex.Pattern; + +import org.bukkit.plugin.Plugin; + +/** + * Represents a String based key which consists of two components - a namespace + * and a key. + * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + * + */ +public final class NamespacedKey { + + /** + * The namespace representing all inbuilt keys. + */ + public static final String MINECRAFT = "minecraft"; + /** + * The namespace representing all keys generated by Bukkit for backwards + * compatibility measures. + */ + public static final String BUKKIT = "bukkit"; + // + private static final Pattern VALID_NAMESPACE = Pattern.compile("[a-z0-9._-]+"); + private static final Pattern VALID_KEY = Pattern.compile("[a-z0-9/._-]+"); + // + private final String namespace; + private final String key; + + /** + * Create a key in a specific namespace. + * + * @param namespace + * @param key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + public NamespacedKey(String namespace, String key) { + Preconditions.checkArgument(namespace != null && VALID_NAMESPACE.matcher(namespace).matches(), "namespace"); + Preconditions.checkArgument(key != null && VALID_KEY.matcher(key).matches(), "key"); + + this.namespace = namespace; + this.key = key; + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedKey must be less than 256 characters", string); + } + + /** + * Create a key in the plugin's namespace. + * + * @param plugin the plugin to use for the namespace + * @param key the key to create + */ + public NamespacedKey(Plugin plugin, String key) { + Preconditions.checkArgument(plugin != null, "plugin"); + Preconditions.checkArgument(key != null, "key"); + + this.namespace = plugin.getName().toLowerCase(Locale.ROOT); + this.key = key.toLowerCase().toLowerCase(Locale.ROOT); + + // Check validity after normalization + Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "namespace"); + Preconditions.checkArgument(VALID_KEY.matcher(this.key).matches(), "key"); + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedKey must be less than 256 characters (%s)", string); + } + + public String getNamespace() { + return namespace; + } + + public String getKey() { + return key; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 47 * hash + this.namespace.hashCode(); + hash = 47 * hash + this.key.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final NamespacedKey other = (NamespacedKey) obj; + return this.namespace.equals(other.namespace) && this.key.equals(other.key); + } + + @Override + public String toString() { + return this.namespace + ":" + this.key; + } + + /** + * Return a new random key in the {@link #BUKKIT} namespace. + * + * @return new key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + public static NamespacedKey randomKey() { + return new NamespacedKey(BUKKIT, UUID.randomUUID().toString()); + } + + /** + * Get a key in the Minecraft namespace. + * + * @param key the key to use + * @return new key in the Minecraft namespace + */ + public static NamespacedKey minecraft(String key) { + return new NamespacedKey(MINECRAFT, key); + } +} diff --git a/src/main/java/org/bukkit/NetherWartsState.java b/src/main/java/org/bukkit/NetherWartsState.java new file mode 100644 index 00000000..f43209cf --- /dev/null +++ b/src/main/java/org/bukkit/NetherWartsState.java @@ -0,0 +1,21 @@ +package org.bukkit; + +public enum NetherWartsState { + + /** + * State when first seeded + */ + SEEDED, + /** + * First growth stage + */ + STAGE_ONE, + /** + * Second growth stage + */ + STAGE_TWO, + /** + * Ready to harvest + */ + RIPE; +} diff --git a/src/main/java/org/bukkit/Note.java b/src/main/java/org/bukkit/Note.java new file mode 100644 index 00000000..af4d118b --- /dev/null +++ b/src/main/java/org/bukkit/Note.java @@ -0,0 +1,276 @@ +package org.bukkit; + +import java.util.Map; + +import org.apache.commons.lang3.Validate; + +import com.google.common.collect.Maps; + +/** + * A note class to store a specific note. + */ +public class Note { + + /** + * An enum holding tones. + */ + public enum Tone { + G(0x1, true), + A(0x3, true), + B(0x5, false), + C(0x6, true), + D(0x8, true), + E(0xA, false), + F(0xB, true); + + private final boolean sharpable; + private final byte id; + + private static final Map BY_DATA = Maps.newHashMap(); + /** The number of tones including sharped tones. */ + public static final byte TONES_COUNT = 12; + + private Tone(int id, boolean sharpable) { + this.id = (byte) (id % TONES_COUNT); + this.sharpable = sharpable; + } + + /** + * Returns the not sharped id of this tone. + * + * @return the not sharped id of this tone. + * @deprecated Magic value + */ + @Deprecated + public byte getId() { + return getId(false); + } + + /** + * Returns the id of this tone. These method allows to return the + * sharped id of the tone. If the tone couldn't be sharped it always + * return the not sharped id of this tone. + * + * @param sharped Set to true to return the sharped id. + * @return the id of this tone. + * @deprecated Magic value + */ + @Deprecated + public byte getId(boolean sharped) { + byte id = (byte) (sharped && sharpable ? this.id + 1 : this.id); + + return (byte) (id % TONES_COUNT); + } + + /** + * Returns if this tone could be sharped. + * + * @return if this tone could be sharped. + */ + public boolean isSharpable() { + return sharpable; + } + + /** + * Returns if this tone id is the sharped id of the tone. + * + * @param id the id of the tone. + * @return if the tone id is the sharped id of the tone. + * @throws IllegalArgumentException if neither the tone nor the + * semitone have the id. + * @deprecated Magic value + */ + @Deprecated + public boolean isSharped(byte id) { + if (id == getId(false)) { + return false; + } else if (id == getId(true)) { + return true; + } else { + // The id isn't matching to the tone! + throw new IllegalArgumentException("The id isn't matching to the tone."); + } + } + + /** + * Returns the tone to id. Also returning the semitones. + * + * @param id the id of the tone. + * @return the tone to id. + * @deprecated Magic value + */ + @Deprecated + public static Tone getById(byte id) { + return BY_DATA.get(id); + } + + static { + for (Tone tone : values()) { + int id = tone.id % TONES_COUNT; + BY_DATA.put((byte) id, tone); + + if (tone.isSharpable()) { + id = (id + 1) % TONES_COUNT; + BY_DATA.put((byte) id, tone); + } + } + } + } + + private final byte note; + + /** + * Creates a new note. + * + * @param note Internal note id. {@link #getId()} always return this + * value. The value has to be in the interval [0; 24]. + */ + public Note(int note) { + Validate.isTrue(note >= 0 && note <= 24, "The note value has to be between 0 and 24."); + + this.note = (byte) note; + } + + /** + * Creates a new note. + * + * @param octave The octave where the note is in. Has to be 0 - 2. + * @param tone The tone within the octave. If the octave is 2 the note has + * to be F#. + * @param sharped Set if the tone is sharped (e.g. for F#). + */ + public Note(int octave, Tone tone, boolean sharped) { + if (sharped && !tone.isSharpable()) { + tone = Tone.values()[tone.ordinal() + 1]; + sharped = false; + } + if (octave < 0 || octave > 2 || (octave == 2 && !(tone == Tone.F && sharped))) { + throw new IllegalArgumentException("Tone and octave have to be between F#0 and F#2"); + } + + this.note = (byte) (octave * Tone.TONES_COUNT + tone.getId(sharped)); + } + + /** + * Creates a new note for a flat tone, such as A-flat. + * + * @param octave The octave where the note is in. Has to be 0 - 1. + * @param tone The tone within the octave. + * @return The new note. + */ + public static Note flat(int octave, Tone tone) { + Validate.isTrue(octave != 2, "Octave cannot be 2 for flats"); + tone = tone == Tone.G ? Tone.F : Tone.values()[tone.ordinal() - 1]; + return new Note(octave, tone, tone.isSharpable()); + } + + /** + * Creates a new note for a sharp tone, such as A-sharp. + * + * @param octave The octave where the note is in. Has to be 0 - 2. + * @param tone The tone within the octave. If the octave is 2 the note has + * to be F#. + * @return The new note. + */ + public static Note sharp(int octave, Tone tone) { + return new Note(octave, tone, true); + } + + /** + * Creates a new note for a natural tone, such as A-natural. + * + * @param octave The octave where the note is in. Has to be 0 - 1. + * @param tone The tone within the octave. + * @return The new note. + */ + public static Note natural(int octave, Tone tone) { + Validate.isTrue(octave != 2, "Octave cannot be 2 for naturals"); + return new Note(octave, tone, false); + } + + /** + * @return The note a semitone above this one. + */ + public Note sharped() { + Validate.isTrue(note < 24, "This note cannot be sharped because it is the highest known note!"); + return new Note(note + 1); + } + + /** + * @return The note a semitone below this one. + */ + public Note flattened() { + Validate.isTrue(note > 0, "This note cannot be flattened because it is the lowest known note!"); + return new Note(note - 1); + } + + /** + * Returns the internal id of this note. + * + * @return the internal id of this note. + * @deprecated Magic value + */ + @Deprecated + public byte getId() { + return note; + } + + /** + * Returns the octave of this note. + * + * @return the octave of this note. + */ + public int getOctave() { + return note / Tone.TONES_COUNT; + } + + private byte getToneByte() { + return (byte) (note % Tone.TONES_COUNT); + } + + /** + * Returns the tone of this note. + * + * @return the tone of this note. + */ + public Tone getTone() { + return Tone.getById(getToneByte()); + } + + /** + * Returns if this note is sharped. + * + * @return if this note is sharped. + */ + public boolean isSharped() { + byte note = getToneByte(); + return Tone.getById(note).isSharped(note); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + note; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Note other = (Note) obj; + if (note != other.note) + return false; + return true; + } + + @Override + public String toString() { + return "Note{" + getTone().toString() + (isSharped() ? "#" : "") + "}"; + } +} diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java new file mode 100644 index 00000000..d8279071 --- /dev/null +++ b/src/main/java/org/bukkit/OfflinePlayer.java @@ -0,0 +1,106 @@ +package org.bukkit; + +import java.util.UUID; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Player; +import org.bukkit.permissions.ServerOperator; + +public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable { + + /** + * Checks if this player is currently online + * + * @return true if they are online + */ + public boolean isOnline(); + + /** + * Returns the name of this player + *

+ * Names are no longer unique past a single game session. For persistent storage + * it is recommended that you use {@link #getUniqueId()} instead. + * + * @return Player name or null if we have not seen a name for this player yet + */ + public String getName(); + + /** + * Returns the UUID of this player + * + * @return Player UUID + */ + public UUID getUniqueId(); + + /** + * Checks if this player is banned or not + * + * @return true if banned, otherwise false + */ + public boolean isBanned(); + + /** + * Checks if this player is whitelisted or not + * + * @return true if whitelisted + */ + public boolean isWhitelisted(); + + /** + * Sets if this player is whitelisted or not + * + * @param value true if whitelisted + */ + public void setWhitelisted(boolean value); + + /** + * Gets a {@link Player} object that this represents, if there is one + *

+ * If the player is online, this will return that player. Otherwise, + * it will return null. + * + * @return Online player + */ + public Player getPlayer(); + + /** + * Gets the first date and time that this player was witnessed on this + * server. + *

+ * If the player has never played before, this will return 0. Otherwise, + * it will be the amount of milliseconds since midnight, January 1, 1970 + * UTC. + * + * @return Date of first log-in for this player, or 0 + */ + public long getFirstPlayed(); + + /** + * Gets the last date and time that this player was witnessed on this + * server. + *

+ * If the player has never played before, this will return 0. Otherwise, + * it will be the amount of milliseconds since midnight, January 1, 1970 + * UTC. + * + * @return Date of last log-in for this player, or 0 + */ + public long getLastPlayed(); + + /** + * Checks if this player has played on this server before. + * + * @return True if the player has played before, otherwise false + */ + public boolean hasPlayedBefore(); + + /** + * Gets the Location where the player will spawn at their bed, null if + * they have not slept in one or their current bed spawn is invalid. + * + * @return Bed Spawn Location if bed exists, otherwise null. + */ + public Location getBedSpawnLocation(); + +} diff --git a/src/main/java/org/bukkit/Particle.java b/src/main/java/org/bukkit/Particle.java new file mode 100644 index 00000000..9794c13e --- /dev/null +++ b/src/main/java/org/bukkit/Particle.java @@ -0,0 +1,74 @@ +package org.bukkit; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +public enum Particle { + EXPLOSION_NORMAL, + EXPLOSION_LARGE, + EXPLOSION_HUGE, + FIREWORKS_SPARK, + WATER_BUBBLE, + WATER_SPLASH, + WATER_WAKE, + SUSPENDED, + SUSPENDED_DEPTH, + CRIT, + CRIT_MAGIC, + SMOKE_NORMAL, + SMOKE_LARGE, + SPELL, + SPELL_INSTANT, + SPELL_MOB, + SPELL_MOB_AMBIENT, + SPELL_WITCH, + DRIP_WATER, + DRIP_LAVA, + VILLAGER_ANGRY, + VILLAGER_HAPPY, + TOWN_AURA, + NOTE, + PORTAL, + ENCHANTMENT_TABLE, + FLAME, + LAVA, + FOOTSTEP, + CLOUD, + REDSTONE, + SNOWBALL, + SNOW_SHOVEL, + SLIME, + HEART, + BARRIER, + ITEM_CRACK(ItemStack.class), + BLOCK_CRACK(MaterialData.class), + BLOCK_DUST(MaterialData.class), + WATER_DROP, + ITEM_TAKE, + MOB_APPEARANCE, + DRAGON_BREATH, + END_ROD, + DAMAGE_INDICATOR, + SWEEP_ATTACK, + FALLING_DUST(MaterialData.class), + TOTEM, + SPIT; + + private final Class dataType; + + Particle() { + dataType = Void.class; + } + + Particle(Class data) { + dataType = data; + } + + /** + * Returns the required data type for the particle + * @return the required data type + */ + public Class getDataType() { + return dataType; + } +} diff --git a/src/main/java/org/bukkit/PortalType.java b/src/main/java/org/bukkit/PortalType.java new file mode 100644 index 00000000..427cfbb8 --- /dev/null +++ b/src/main/java/org/bukkit/PortalType.java @@ -0,0 +1,22 @@ +package org.bukkit; + +/** + * Represents various types of portals that can be made in a world. + */ +public enum PortalType { + + /** + * This is a Nether portal, made of obsidian. + */ + NETHER, + + /** + * This is an Ender portal. + */ + ENDER, + + /** + * This is a custom Plugin portal. + */ + CUSTOM; +} diff --git a/src/main/java/org/bukkit/Rotation.java b/src/main/java/org/bukkit/Rotation.java new file mode 100644 index 00000000..8afd0469 --- /dev/null +++ b/src/main/java/org/bukkit/Rotation.java @@ -0,0 +1,63 @@ +package org.bukkit; + +/** + * An enum to specify a rotation based orientation, like that on a clock. + *

+ * It represents how something is viewed, as opposed to cardinal directions. + */ +public enum Rotation { + + /** + * No rotation + */ + NONE, + /** + * Rotated clockwise by 45 degrees + */ + CLOCKWISE_45, + /** + * Rotated clockwise by 90 degrees + */ + CLOCKWISE, + /** + * Rotated clockwise by 135 degrees + */ + CLOCKWISE_135, + /** + * Flipped upside-down, a 180 degree rotation + */ + FLIPPED, + /** + * Flipped upside-down + 45 degree rotation + */ + FLIPPED_45, + /** + * Rotated counter-clockwise by 90 degrees + */ + COUNTER_CLOCKWISE, + /** + * Rotated counter-clockwise by 45 degrees + */ + COUNTER_CLOCKWISE_45 + ; + + private static final Rotation[] rotations = values(); + + /** + * Rotate clockwise by 90 degrees. + * + * @return the relative rotation + */ + public Rotation rotateClockwise() { + return rotations[(this.ordinal() + 1) & 0x7]; + } + + /** + * Rotate counter-clockwise by 90 degrees. + * + * @return the relative rotation + */ + public Rotation rotateCounterClockwise() { + return rotations[(this.ordinal() - 1) & 0x7]; + } +} diff --git a/src/main/java/org/bukkit/SandstoneType.java b/src/main/java/org/bukkit/SandstoneType.java new file mode 100644 index 00000000..a9ac16e7 --- /dev/null +++ b/src/main/java/org/bukkit/SandstoneType.java @@ -0,0 +1,51 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the three different types of Sandstone + */ +public enum SandstoneType { + CRACKED(0x0), + GLYPHED(0x1), + SMOOTH(0x2); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private SandstoneType(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this type of sandstone + * + * @return A byte containing the data value of this sandstone type + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the type of sandstone with the given data value + * + * @param data Data value to fetch + * @return The {@link SandstoneType} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static SandstoneType getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (SandstoneType type : values()) { + BY_DATA.put(type.data, type); + } + } +} diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java new file mode 100644 index 00000000..372c9908 --- /dev/null +++ b/src/main/java/org/bukkit/Server.java @@ -0,0 +1,1037 @@ +package org.bukkit; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bukkit.Warning.WarningState; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.command.*; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Recipe; +import org.bukkit.map.MapView; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.util.CachedServerIcon; + +import com.google.common.collect.ImmutableList; +import org.bukkit.advancement.Advancement; +import org.bukkit.generator.ChunkGenerator; + +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Represents a server implementation. + */ +public interface Server extends PluginMessageRecipient { + + /** + * Used for all administrative messages, such as an operator using a + * command. + *

+ * For use in {@link #broadcast(String, String)}. + */ + public static final String BROADCAST_CHANNEL_ADMINISTRATIVE = "bukkit.broadcast.admin"; + + /** + * Used for all announcement messages, such as informing users that a + * player has joined. + *

+ * For use in {@link #broadcast(String, String)}. + */ + public static final String BROADCAST_CHANNEL_USERS = "bukkit.broadcast.user"; + + /** + * Gets the name of this server implementation. + * + * @return name of this server implementation + */ + public String getName(); + + /** + * Gets the version string of this server implementation. + * + * @return version of this server implementation + */ + public String getVersion(); + + /** + * Gets the Bukkit version that this server is running. + * + * @return version of Bukkit + */ + String getBukkitVersion(); + + public Player[] _INVALID_getOnlinePlayers(); + + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused + * object, making some operations like {@link Collection#size()} + * zero-allocation. + *

+ * The collection is a view backed by the internal representation, such + * that, changes to the internal state of the server will be reflected + * immediately. However, the reuse of the returned collection (identity) + * is not strictly guaranteed for future or all implementations. Casting + * the collection, or relying on interface implementations (like {@link + * Serializable} or {@link List}), is deprecated. + *

+ * Iteration behavior is undefined outside of self-contained main-thread + * uses. Normal and immediate iterator use without consequences that + * affect the collection are fully supported. The effects following + * (non-exhaustive) {@link Entity#teleport(Location) teleportation}, + * {@link Player#setHealth(double) death}, and {@link Player#kickPlayer( + * String) kicking} are undefined. Any use of this collection from + * asynchronous threads is unsafe. + *

+ * For safe consequential iteration or mimicking the old array behavior, + * using {@link Collection#toArray(Object[])} is recommended. For making + * snapshots, {@link ImmutableList#copyOf(Collection)} is recommended. + * + * @return a view of currently online players. + */ + public Collection getOnlinePlayers(); + + /** + * Get the maximum amount of players which can login to this server. + * + * @return the amount of players this server allows + */ + public int getMaxPlayers(); + + /** + * Get the game port that the server runs on. + * + * @return the port number of this server + */ + public int getPort(); + + /** + * Get the view distance from this server. + * + * @return the view distance from this server. + */ + public int getViewDistance(); + + /** + * Get the IP that this server is bound to, or empty string if not + * specified. + * + * @return the IP string that this server is bound to, otherwise empty + * string + */ + public String getIp(); + + /** + * Get the name of this server. + * + * @return the name of this server + */ + public String getServerName(); + + /** + * Get an ID of this server. The ID is a simple generally alphanumeric ID + * that can be used for uniquely identifying this server. + * + * @return the ID of this server + */ + public String getServerId(); + + /** + * Get world type (level-type setting) for default world. + * + * @return the value of level-type (e.g. DEFAULT, FLAT, DEFAULT_1_1) + */ + public String getWorldType(); + + /** + * Get generate-structures setting. + * + * @return true if structure generation is enabled, false otherwise + */ + public boolean getGenerateStructures(); + + /** + * Gets whether this server allows the End or not. + * + * @return whether this server allows the End or not + */ + public boolean getAllowEnd(); + + /** + * Gets whether this server allows the Nether or not. + * + * @return whether this server allows the Nether or not + */ + public boolean getAllowNether(); + + /** + * Gets whether this server has a whitelist or not. + * + * @return whether this server has a whitelist or not + */ + public boolean hasWhitelist(); + + /** + * Sets if the server is whitelisted. + * + * @param value true for whitelist on, false for off + */ + public void setWhitelist(boolean value); + + /** + * Gets a list of whitelisted players. + * + * @return a set containing all whitelisted players + */ + public Set getWhitelistedPlayers(); + + /** + * Reloads the whitelist from disk. + */ + public void reloadWhitelist(); + + /** + * Broadcast a message to all players. + *

+ * This is the same as calling {@link #broadcast(String, + * String)} to {@link #BROADCAST_CHANNEL_USERS} + * + * @param message the message + * @return the number of players + */ + public int broadcastMessage(String message); + + /** + * Gets the name of the update folder. The update folder is used to safely + * update plugins at the right moment on a plugin load. + *

+ * The update folder name is relative to the plugins folder. + * + * @return the name of the update folder + */ + public String getUpdateFolder(); + + /** + * Gets the update folder. The update folder is used to safely update + * plugins at the right moment on a plugin load. + * + * @return the update folder + */ + public File getUpdateFolderFile(); + + /** + * Gets the value of the connection throttle setting. + * + * @return the value of the connection throttle setting + */ + public long getConnectionThrottle(); + + /** + * Gets default ticks per animal spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, animal spawning will be disabled. We + * recommend using spawn-animals to control this instead. + *

+ * Minecraft default: 400. + * + * @return the default ticks per animal spawns value + */ + public int getTicksPerAnimalSpawns(); + + /** + * Gets the default ticks per monster spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, monsters spawning will be disabled. We + * recommend using spawn-monsters to control this instead. + *

+ * Minecraft default: 1. + * + * @return the default ticks per monsters spawn value + */ + public int getTicksPerMonsterSpawns(); + + /** + * Gets a player object by the given username. + *

+ * This method may not return objects for offline players. + * + * @param name the name to look up + * @return a player if one was found, null otherwise + */ + public Player getPlayer(String name); + + /** + * Gets the player with the exact given name, case insensitive. + * + * @param name Exact name of the player to retrieve + * @return a player object if one was found, null otherwise + */ + public Player getPlayerExact(String name); + + /** + * Attempts to match any players with the given name, and returns a list + * of all possibly matches. + *

+ * This list is not sorted in any particular order. If an exact match is + * found, the returned list will only contain a single result. + * + * @param name the (partial) name to match + * @return list of all possible players + */ + public List matchPlayer(String name); + + /** + * Gets the player with the given UUID. + * + * @param id UUID of the player to retrieve + * @return a player object if one was found, null otherwise + */ + Player getPlayer(UUID id); + + @Nullable + UUID getPlayerUniqueId(String playerName); + + /** + * Gets the plugin manager for interfacing with plugins. + * + * @return a plugin manager for this Server instance + */ + public PluginManager getPluginManager(); + + /** + * Gets the scheduler for managing scheduled events. + * + * @return a scheduling service for this server + */ + public BukkitScheduler getScheduler(); + + /** + * Gets a services manager. + * + * @return s services manager + */ + public ServicesManager getServicesManager(); + + /** + * Gets a list of all worlds on this server. + * + * @return a list of worlds + */ + public List getWorlds(); + + /** + * Creates or loads a world with the given name using the specified + * options. + *

+ * If the world is already loaded, it will just return the equivalent of + * getWorld(creator.name()). + * + * @param creator the options to use when creating the world + * @return newly created or loaded world + */ + public World createWorld(WorldCreator creator); + + /** + * Unloads a world with the given name. + * + * @param name Name of the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public boolean unloadWorld(String name, boolean save); + + /** + * Unloads the given world. + * + * @param world the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public boolean unloadWorld(World world, boolean save); + + /** + * Gets the world with the given name. + * + * @param name the name of the world to retrieve + * @return a world with the given name, or null if none exists + */ + public World getWorld(String name); + + /** + * Gets the world from the given Unique ID. + * + * @param uid a unique-id of the world to retrieve + * @return a world with the given Unique ID, or null if none exists + */ + public World getWorld(UUID uid); + + /** + * Gets the map from the given item ID. + * + * @param id the id of the map to get + * @return a map view if it exists, or null otherwise + * @deprecated Magic value + */ + @Deprecated + public MapView getMap(short id); + + /** + * Create a new map with an automatically assigned ID. + * + * @param world the world the map will belong to + * @return a newly created map view + */ + public MapView createMap(World world); + + /** + * Reloads the server, refreshing settings and plugin information. + */ + public void reload(); + + /** + * Reload only the Minecraft data for the server. This includes custom + * advancements and loot tables. + */ + public void reloadData(); + + /** + * Returns the primary logger associated with this server instance. + * + * @return Logger associated with this server + */ + public Logger getLogger(); + + /** + * Gets a {@link PluginCommand} with the given name or alias. + * + * @param name the name of the command to retrieve + * @return a plugin command if found, null otherwise + */ + public PluginCommand getPluginCommand(String name); + + /** + * Writes loaded players to disk. + */ + public void savePlayers(); + + /** + * Dispatches a command on this server, and executes it if found. + * + * @param sender the apparent sender of the command + * @param commandLine the command + arguments. Example: test abc + * 123 + * @return returns false if no target is found + * @throws CommandException thrown when the executor for the given command + * fails with an unhandled exception + */ + public boolean dispatchCommand(CommandSender sender, String commandLine) throws CommandException; + + /** + * Adds a recipe to the crafting manager. + * + * @param recipe the recipe to add + * @return true if the recipe was added, false if it wasn't for some + * reason + */ + public boolean addRecipe(Recipe recipe); + + /** + * Get a list of all recipes for a given item. The stack size is ignored + * in comparisons. If the durability is -1, it will match any data value. + * + * @param result the item to match against recipe results + * @return a list of recipes with the given result + */ + public List getRecipesFor(ItemStack result); + + /** + * Get an iterator through the list of crafting recipes. + * + * @return an iterator + */ + public Iterator recipeIterator(); + + /** + * Clears the list of crafting recipes. + */ + public void clearRecipes(); + + /** + * Resets the list of crafting recipes to the default. + */ + public void resetRecipes(); + + /** + * Gets a list of command aliases defined in the server properties. + * + * @return a map of aliases to command names + */ + public Map getCommandAliases(); + + /** + * Gets the radius, in blocks, around each worlds spawn point to protect. + * + * @return spawn radius, or 0 if none + */ + public int getSpawnRadius(); + + /** + * Sets the radius, in blocks, around each worlds spawn point to protect. + * + * @param value new spawn radius, or 0 if none + */ + public void setSpawnRadius(int value); + + /** + * Gets whether the Server is in online mode or not. + * + * @return true if the server authenticates clients, false otherwise + */ + public boolean getOnlineMode(); + + /** + * Gets whether this server allows flying or not. + * + * @return true if the server allows flight, false otherwise + */ + public boolean getAllowFlight(); + + /** + * Gets whether the server is in hardcore mode or not. + * + * @return true if the server mode is hardcore, false otherwise + */ + public boolean isHardcore(); + + /** + * Shutdowns the server, stopping everything. + */ + public void shutdown(); + + /** + * Broadcasts the specified message to every user with the given + * permission name. + * + * @param message message to broadcast + * @param permission the required permission {@link Permissible + * permissibles} must have to receive the broadcast + * @return number of message recipients + */ + public int broadcast(String message, String permission); + + /** + * Gets the player by the given name, regardless if they are offline or + * online. + *

+ * This method may involve a blocking web request to get the UUID for the + * given name. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @deprecated Persistent storage of users should be by UUID as names are no longer + * unique past a single session. + * @param name the name the player to retrieve + * @return an offline player + * @see #getOfflinePlayer(UUID) + */ + @Deprecated + public OfflinePlayer getOfflinePlayer(String name); + + /** + * Gets the player by the given UUID, regardless if they are offline or + * online. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @param id the UUID of the player to retrieve + * @return an offline player + */ + public OfflinePlayer getOfflinePlayer(UUID id); + + /** + * Gets a set containing all current IPs that are banned. + * + * @return a set containing banned IP addresses + */ + public Set getIPBans(); + + /** + * Bans the specified address from the server. + * + * @param address the IP address to ban + */ + public void banIP(String address); + + /** + * Unbans the specified address from the server. + * + * @param address the IP address to unban + */ + public void unbanIP(String address); + + /** + * Gets a set containing all banned players. + * + * @return a set containing banned players + */ + public Set getBannedPlayers(); + + /** + * Gets a ban list for the supplied type. + *

+ * Bans by name are no longer supported and this method will return + * null when trying to request them. The replacement is bans by UUID. + * + * @param type the type of list to fetch, cannot be null + * @return a ban list of the specified type + */ + public BanList getBanList(BanList.Type type); + + /** + * Gets a set containing all player operators. + * + * @return a set containing player operators + */ + public Set getOperators(); + + /** + * Gets the default {@link GameMode} for new players. + * + * @return the default game mode + */ + public GameMode getDefaultGameMode(); + + /** + * Sets the default {@link GameMode} for new players. + * + * @param mode the new game mode + */ + public void setDefaultGameMode(GameMode mode); + + /** + * Gets a {@link ConsoleCommandSender} that may be used as an input source + * for this server. + * + * @return a console command sender + */ + public ConsoleCommandSender getConsoleSender(); + + /** + * Gets the folder that contains all of the various {@link World}s. + * + * @return folder that contains all worlds + */ + public File getWorldContainer(); + + /** + * Gets every player that has ever played on this server. + * + * @return an array containing all previous players + */ + public OfflinePlayer[] getOfflinePlayers(); + + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return messenger responsible for this server + */ + public Messenger getMessenger(); + + /** + * Gets the {@link HelpMap} providing help topics for this server. + * + * @return a help map for this server + */ + public HelpMap getHelpMap(); + + /** + * Creates an empty inventory of the specified type. If the type is {@link + * InventoryType#CHEST}, the new inventory has a size of 27; otherwise the + * new inventory has the normal size for its type. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param type the type of inventory to create + * @return a new inventory + */ + Inventory createInventory(InventoryHolder owner, InventoryType type); + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + * + * @param owner The holder of the inventory; can be null if there's no holder. + * @param type The type of inventory to create. + * @param title The title of the inventory, to be displayed when it is viewed. + * @return The new inventory. + */ + Inventory createInventory(InventoryHolder owner, InventoryType type, String title); + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException; + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size and title. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @param title the title of the inventory, displayed when inventory is + * viewed + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException; + + /** + * Creates an empty merchant. + * + * @param title the title of the corresponding merchant inventory, displayed + * when the merchant inventory is viewed + * @return a new merchant + */ + Merchant createMerchant(String title); + + /** + * Gets user-specified limit for number of monsters that can spawn in a + * chunk. + * + * @return the monster spawn limit + */ + int getMonsterSpawnLimit(); + + /** + * Gets user-specified limit for number of animals that can spawn in a + * chunk. + * + * @return the animal spawn limit + */ + int getAnimalSpawnLimit(); + + /** + * Gets user-specified limit for number of water animals that can spawn in + * a chunk. + * + * @return the water animal spawn limit + */ + int getWaterAnimalSpawnLimit(); + + /** + * Gets user-specified limit for number of ambient mobs that can spawn in + * a chunk. + * + * @return the ambient spawn limit + */ + int getAmbientSpawnLimit(); + + /** + * Checks the current thread against the expected primary thread for the + * server. + *

+ * Note: this method should not be used to indicate the current + * synchronized state of the runtime. A current thread matching the main + * thread indicates that it is synchronized, but a mismatch does not + * preclude the same assumption. + * + * @return true if the current thread matches the expected primary thread, + * false otherwise + */ + boolean isPrimaryThread(); + + /** + * Gets the message that is displayed on the server list. + * + * @return the servers MOTD + */ + String getMotd(); + + /** + * Gets the default message that is displayed when the server is stopped. + * + * @return the shutdown message + */ + String getShutdownMessage(); + + /** + * Gets the current warning state for the server. + * + * @return the configured warning state + */ + public WarningState getWarningState(); + + /** + * Gets the instance of the item factory (for {@link ItemMeta}). + * + * @return the item factory + * @see ItemFactory + */ + ItemFactory getItemFactory(); + + /** + * Gets the instance of the scoreboard manager. + *

+ * This will only exist after the first world has loaded. + * + * @return the scoreboard manager or null if no worlds are loaded. + */ + ScoreboardManager getScoreboardManager(); + + /** + * Gets an instance of the server's default server-icon. + * + * @return the default server-icon; null values may be used by the + * implementation to indicate no defined icon, but this behavior is + * not guaranteed + */ + CachedServerIcon getServerIcon(); + + /** + * Loads an image from a file, and returns a cached image for the specific + * server-icon. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param file the file to load the from + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server server-icon + * specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + CachedServerIcon loadServerIcon(File file) throws IllegalArgumentException, Exception; + + /** + * Creates a cached server-icon for the specific image. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param image the image to use + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server + * server-icon specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + CachedServerIcon loadServerIcon(BufferedImage image) throws IllegalArgumentException, Exception; + + /** + * Set the idle kick timeout. Any players idle for the specified amount of + * time will be automatically kicked. + *

+ * A value of 0 will disable the idle kick timeout. + * + * @param threshold the idle timeout in minutes + */ + public void setIdleTimeout(int threshold); + + /** + * Gets the idle kick timeout. + * + * @return the idle timeout in minutes + */ + public int getIdleTimeout(); + + /** + * Create a ChunkData for use in a generator. + * + * See {@link ChunkGenerator#generateChunkData(World, java.util.Random, int, int, ChunkGenerator.BiomeGrid)} + * + * @param world the world to create the ChunkData for + * @return a new ChunkData for the world + * + */ + public ChunkGenerator.ChunkData createChunkData(World world); + + /** + * Creates a boss bar instance to display to players. The progress + * defaults to 1.0 + * + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags); + + /** + * Gets an entity on the server by its UUID + * + * @param uuid the UUID of the entity + * @return the entity with the given UUID, or null if it isn't found + */ + Entity getEntity(UUID uuid); + + // Paper start + /** + * Gets the current server TPS + * + * @return current server TPS (1m, 5m, 15m in Paper-Server) + */ + double[] getTPS(); + + /** + * Gets the active {@link CommandMap} + * + * @return the active command map + */ + CommandMap getCommandMap(); + // Paper end + + /** + * Get the advancement specified by this key. + * + * @param key unique advancement key + * @return advancement or null if not exists + */ + Advancement getAdvancement(NamespacedKey key); + + /** + * Get an iterator through all advancements. Advancements cannot be removed + * from this iterator, + * + * @return an advancement iterator + */ + Iterator advancementIterator(); + + /** + * @see UnsafeValues + * @return the unsafe values instance + */ + @Deprecated + UnsafeValues getUnsafe(); + + // Spigot start + public class Spigot + { + + public org.bukkit.configuration.file.YamlConfiguration getConfig() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Sends the component to the player + * + * @param component the components to send + */ + public void broadcast(net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the player + * + * @param components the components to send + */ + public void broadcast(net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Restart the server. If the server administrator has not configured restarting, the server will stop. + */ + public void restart() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + Spigot spigot(); + // Spigot end + + // Paper start - allow preventing player name suggestions by default + /** + * Checks if player names should be suggested when a command returns {@code null} as + * their tab completion result. + * + * @return true if player names should be suggested + */ + boolean suggestPlayerNamesWhenNullTabCompletions(); + + /** + * Creates a PlayerProfile for the specified uuid, with name as null + * @param uuid UUID to create profile for + * @return A PlayerProfile object + */ + com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid); + + /** + * Creates a PlayerProfile for the specified name, with UUID as null + * @param name Name to create profile for + * @return A PlayerProfile object + */ + com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name); + + /** + * Creates a PlayerProfile for the specified name/uuid + * + * Both UUID and Name can not be null at same time. One must be supplied. + * + * @param uuid UUID to create profile for + * @param name Name to create profile for + * @return A PlayerProfile object + */ + com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name); + // Paper end +} diff --git a/src/main/java/org/bukkit/SkullType.java b/src/main/java/org/bukkit/SkullType.java new file mode 100644 index 00000000..db651d50 --- /dev/null +++ b/src/main/java/org/bukkit/SkullType.java @@ -0,0 +1,13 @@ +package org.bukkit; + +/** + * Represents the different types of skulls. + */ +public enum SkullType { + SKELETON, + WITHER, + ZOMBIE, + PLAYER, + CREEPER, + DRAGON; +} diff --git a/src/main/java/org/bukkit/Sound.java b/src/main/java/org/bukkit/Sound.java new file mode 100644 index 00000000..82d75054 --- /dev/null +++ b/src/main/java/org/bukkit/Sound.java @@ -0,0 +1,561 @@ +package org.bukkit; + +/** + * An Enum of Sounds the server is able to send to players. + *

+ * WARNING: At any time, sounds may be added/removed from this Enum or even + * MineCraft itself! There is no guarantee the sounds will play. There is no + * guarantee values will not be removed from this Enum. As such, you should not + * depend on the ordinal values of this class. + */ +public enum Sound { + AMBIENT_CAVE, + BLOCK_ANVIL_BREAK, + BLOCK_ANVIL_DESTROY, + BLOCK_ANVIL_FALL, + BLOCK_ANVIL_HIT, + BLOCK_ANVIL_LAND, + BLOCK_ANVIL_PLACE, + BLOCK_ANVIL_STEP, + BLOCK_ANVIL_USE, + BLOCK_BREWING_STAND_BREW, + BLOCK_CHEST_CLOSE, + BLOCK_CHEST_LOCKED, + BLOCK_CHEST_OPEN, + BLOCK_CHORUS_FLOWER_DEATH, + BLOCK_CHORUS_FLOWER_GROW, + BLOCK_CLOTH_BREAK, + BLOCK_CLOTH_FALL, + BLOCK_CLOTH_HIT, + BLOCK_CLOTH_PLACE, + BLOCK_CLOTH_STEP, + BLOCK_COMPARATOR_CLICK, + BLOCK_DISPENSER_DISPENSE, + BLOCK_DISPENSER_FAIL, + BLOCK_DISPENSER_LAUNCH, + BLOCK_ENCHANTMENT_TABLE_USE, + BLOCK_ENDERCHEST_CLOSE, + BLOCK_ENDERCHEST_OPEN, + BLOCK_END_GATEWAY_SPAWN, + BLOCK_END_PORTAL_FRAME_FILL, + BLOCK_END_PORTAL_SPAWN, + BLOCK_FENCE_GATE_CLOSE, + BLOCK_FENCE_GATE_OPEN, + BLOCK_FIRE_AMBIENT, + BLOCK_FIRE_EXTINGUISH, + BLOCK_FURNACE_FIRE_CRACKLE, + BLOCK_GLASS_BREAK, + BLOCK_GLASS_FALL, + BLOCK_GLASS_HIT, + BLOCK_GLASS_PLACE, + BLOCK_GLASS_STEP, + BLOCK_GRASS_BREAK, + BLOCK_GRASS_FALL, + BLOCK_GRASS_HIT, + BLOCK_GRASS_PLACE, + BLOCK_GRASS_STEP, + BLOCK_GRAVEL_BREAK, + BLOCK_GRAVEL_FALL, + BLOCK_GRAVEL_HIT, + BLOCK_GRAVEL_PLACE, + BLOCK_GRAVEL_STEP, + BLOCK_IRON_DOOR_CLOSE, + BLOCK_IRON_DOOR_OPEN, + BLOCK_IRON_TRAPDOOR_CLOSE, + BLOCK_IRON_TRAPDOOR_OPEN, + BLOCK_LADDER_BREAK, + BLOCK_LADDER_FALL, + BLOCK_LADDER_HIT, + BLOCK_LADDER_PLACE, + BLOCK_LADDER_STEP, + BLOCK_LAVA_AMBIENT, + BLOCK_LAVA_EXTINGUISH, + BLOCK_LAVA_POP, + BLOCK_LEVER_CLICK, + BLOCK_METAL_BREAK, + BLOCK_METAL_FALL, + BLOCK_METAL_HIT, + BLOCK_METAL_PLACE, + BLOCK_METAL_PRESSUREPLATE_CLICK_OFF, + BLOCK_METAL_PRESSUREPLATE_CLICK_ON, + BLOCK_METAL_STEP, + BLOCK_NOTE_BASEDRUM, + BLOCK_NOTE_BASS, + BLOCK_NOTE_BELL, + BLOCK_NOTE_CHIME, + BLOCK_NOTE_FLUTE, + BLOCK_NOTE_GUITAR, + BLOCK_NOTE_HARP, + BLOCK_NOTE_HAT, + BLOCK_NOTE_PLING, + BLOCK_NOTE_SNARE, + BLOCK_NOTE_XYLOPHONE, + BLOCK_PISTON_CONTRACT, + BLOCK_PISTON_EXTEND, + BLOCK_PORTAL_AMBIENT, + BLOCK_PORTAL_TRAVEL, + BLOCK_PORTAL_TRIGGER, + BLOCK_REDSTONE_TORCH_BURNOUT, + BLOCK_SAND_BREAK, + BLOCK_SAND_FALL, + BLOCK_SAND_HIT, + BLOCK_SAND_PLACE, + BLOCK_SAND_STEP, + BLOCK_SHULKER_BOX_CLOSE, + BLOCK_SHULKER_BOX_OPEN, + BLOCK_SLIME_BREAK, + BLOCK_SLIME_FALL, + BLOCK_SLIME_HIT, + BLOCK_SLIME_PLACE, + BLOCK_SLIME_STEP, + BLOCK_SNOW_BREAK, + BLOCK_SNOW_FALL, + BLOCK_SNOW_HIT, + BLOCK_SNOW_PLACE, + BLOCK_SNOW_STEP, + BLOCK_STONE_BREAK, + BLOCK_STONE_BUTTON_CLICK_OFF, + BLOCK_STONE_BUTTON_CLICK_ON, + BLOCK_STONE_FALL, + BLOCK_STONE_HIT, + BLOCK_STONE_PLACE, + BLOCK_STONE_PRESSUREPLATE_CLICK_OFF, + BLOCK_STONE_PRESSUREPLATE_CLICK_ON, + BLOCK_STONE_STEP, + BLOCK_TRIPWIRE_ATTACH, + BLOCK_TRIPWIRE_CLICK_OFF, + BLOCK_TRIPWIRE_CLICK_ON, + BLOCK_TRIPWIRE_DETACH, + BLOCK_WATERLILY_PLACE, + BLOCK_WATER_AMBIENT, + BLOCK_WOODEN_DOOR_CLOSE, + BLOCK_WOODEN_DOOR_OPEN, + BLOCK_WOODEN_TRAPDOOR_CLOSE, + BLOCK_WOODEN_TRAPDOOR_OPEN, + BLOCK_WOOD_BREAK, + BLOCK_WOOD_BUTTON_CLICK_OFF, + BLOCK_WOOD_BUTTON_CLICK_ON, + BLOCK_WOOD_FALL, + BLOCK_WOOD_HIT, + BLOCK_WOOD_PLACE, + BLOCK_WOOD_PRESSUREPLATE_CLICK_OFF, + BLOCK_WOOD_PRESSUREPLATE_CLICK_ON, + BLOCK_WOOD_STEP, + ENCHANT_THORNS_HIT, + ENTITY_ARMORSTAND_BREAK, + ENTITY_ARMORSTAND_FALL, + ENTITY_ARMORSTAND_HIT, + ENTITY_ARMORSTAND_PLACE, + ENTITY_ARROW_HIT, + ENTITY_ARROW_HIT_PLAYER, + ENTITY_ARROW_SHOOT, + ENTITY_BAT_AMBIENT, + ENTITY_BAT_DEATH, + ENTITY_BAT_HURT, + ENTITY_BAT_LOOP, + ENTITY_BAT_TAKEOFF, + ENTITY_BLAZE_AMBIENT, + ENTITY_BLAZE_BURN, + ENTITY_BLAZE_DEATH, + ENTITY_BLAZE_HURT, + ENTITY_BLAZE_SHOOT, + ENTITY_BOAT_PADDLE_LAND, + ENTITY_BOAT_PADDLE_WATER, + ENTITY_BOBBER_RETRIEVE, + ENTITY_BOBBER_SPLASH, + ENTITY_BOBBER_THROW, + ENTITY_CAT_AMBIENT, + ENTITY_CAT_DEATH, + ENTITY_CAT_HISS, + ENTITY_CAT_HURT, + ENTITY_CAT_PURR, + ENTITY_CAT_PURREOW, + ENTITY_CHICKEN_AMBIENT, + ENTITY_CHICKEN_DEATH, + ENTITY_CHICKEN_EGG, + ENTITY_CHICKEN_HURT, + ENTITY_CHICKEN_STEP, + ENTITY_COW_AMBIENT, + ENTITY_COW_DEATH, + ENTITY_COW_HURT, + ENTITY_COW_MILK, + ENTITY_COW_STEP, + ENTITY_CREEPER_DEATH, + ENTITY_CREEPER_HURT, + ENTITY_CREEPER_PRIMED, + ENTITY_DONKEY_AMBIENT, + ENTITY_DONKEY_ANGRY, + ENTITY_DONKEY_CHEST, + ENTITY_DONKEY_DEATH, + ENTITY_DONKEY_HURT, + ENTITY_EGG_THROW, + ENTITY_ELDER_GUARDIAN_AMBIENT, + ENTITY_ELDER_GUARDIAN_AMBIENT_LAND, + ENTITY_ELDER_GUARDIAN_CURSE, + ENTITY_ELDER_GUARDIAN_DEATH, + ENTITY_ELDER_GUARDIAN_DEATH_LAND, + ENTITY_ELDER_GUARDIAN_FLOP, + ENTITY_ELDER_GUARDIAN_HURT, + ENTITY_ELDER_GUARDIAN_HURT_LAND, + ENTITY_ENDERDRAGON_AMBIENT, + ENTITY_ENDERDRAGON_DEATH, + ENTITY_ENDERDRAGON_FIREBALL_EXPLODE, + ENTITY_ENDERDRAGON_FLAP, + ENTITY_ENDERDRAGON_GROWL, + ENTITY_ENDERDRAGON_HURT, + ENTITY_ENDERDRAGON_SHOOT, + ENTITY_ENDEREYE_DEATH, + ENTITY_ENDEREYE_LAUNCH, + ENTITY_ENDERMEN_AMBIENT, + ENTITY_ENDERMEN_DEATH, + ENTITY_ENDERMEN_HURT, + ENTITY_ENDERMEN_SCREAM, + ENTITY_ENDERMEN_STARE, + ENTITY_ENDERMEN_TELEPORT, + ENTITY_ENDERMITE_AMBIENT, + ENTITY_ENDERMITE_DEATH, + ENTITY_ENDERMITE_HURT, + ENTITY_ENDERMITE_STEP, + ENTITY_ENDERPEARL_THROW, + ENTITY_EVOCATION_FANGS_ATTACK, + ENTITY_EVOCATION_ILLAGER_AMBIENT, + ENTITY_EVOCATION_ILLAGER_CAST_SPELL, + ENTITY_EVOCATION_ILLAGER_DEATH, + ENTITY_EVOCATION_ILLAGER_HURT, + ENTITY_EVOCATION_ILLAGER_PREPARE_ATTACK, + ENTITY_EVOCATION_ILLAGER_PREPARE_SUMMON, + ENTITY_EVOCATION_ILLAGER_PREPARE_WOLOLO, + ENTITY_EXPERIENCE_BOTTLE_THROW, + ENTITY_EXPERIENCE_ORB_PICKUP, + ENTITY_FIREWORK_BLAST, + ENTITY_FIREWORK_BLAST_FAR, + ENTITY_FIREWORK_LARGE_BLAST, + ENTITY_FIREWORK_LARGE_BLAST_FAR, + ENTITY_FIREWORK_LAUNCH, + ENTITY_FIREWORK_SHOOT, + ENTITY_FIREWORK_TWINKLE, + ENTITY_FIREWORK_TWINKLE_FAR, + ENTITY_GENERIC_BIG_FALL, + ENTITY_GENERIC_BURN, + ENTITY_GENERIC_DEATH, + ENTITY_GENERIC_DRINK, + ENTITY_GENERIC_EAT, + ENTITY_GENERIC_EXPLODE, + ENTITY_GENERIC_EXTINGUISH_FIRE, + ENTITY_GENERIC_HURT, + ENTITY_GENERIC_SMALL_FALL, + ENTITY_GENERIC_SPLASH, + ENTITY_GENERIC_SWIM, + ENTITY_GHAST_AMBIENT, + ENTITY_GHAST_DEATH, + ENTITY_GHAST_HURT, + ENTITY_GHAST_SCREAM, + ENTITY_GHAST_SHOOT, + ENTITY_GHAST_WARN, + ENTITY_GUARDIAN_AMBIENT, + ENTITY_GUARDIAN_AMBIENT_LAND, + ENTITY_GUARDIAN_ATTACK, + ENTITY_GUARDIAN_DEATH, + ENTITY_GUARDIAN_DEATH_LAND, + ENTITY_GUARDIAN_FLOP, + ENTITY_GUARDIAN_HURT, + ENTITY_GUARDIAN_HURT_LAND, + ENTITY_HORSE_AMBIENT, + ENTITY_HORSE_ANGRY, + ENTITY_HORSE_ARMOR, + ENTITY_HORSE_BREATHE, + ENTITY_HORSE_DEATH, + ENTITY_HORSE_EAT, + ENTITY_HORSE_GALLOP, + ENTITY_HORSE_HURT, + ENTITY_HORSE_JUMP, + ENTITY_HORSE_LAND, + ENTITY_HORSE_SADDLE, + ENTITY_HORSE_STEP, + ENTITY_HORSE_STEP_WOOD, + ENTITY_HOSTILE_BIG_FALL, + ENTITY_HOSTILE_DEATH, + ENTITY_HOSTILE_HURT, + ENTITY_HOSTILE_SMALL_FALL, + ENTITY_HOSTILE_SPLASH, + ENTITY_HOSTILE_SWIM, + ENTITY_HUSK_AMBIENT, + ENTITY_HUSK_DEATH, + ENTITY_HUSK_HURT, + ENTITY_HUSK_STEP, + ENTITY_ILLUSION_ILLAGER_AMBIENT, + ENTITY_ILLUSION_ILLAGER_CAST_SPELL, + ENTITY_ILLUSION_ILLAGER_DEATH, + ENTITY_ILLUSION_ILLAGER_HURT, + ENTITY_ILLUSION_ILLAGER_MIRROR_MOVE, + ENTITY_ILLUSION_ILLAGER_PREPARE_BLINDNESS, + ENTITY_ILLUSION_ILLAGER_PREPARE_MIRROR, + ENTITY_IRONGOLEM_ATTACK, + ENTITY_IRONGOLEM_DEATH, + ENTITY_IRONGOLEM_HURT, + ENTITY_IRONGOLEM_STEP, + ENTITY_ITEMFRAME_ADD_ITEM, + ENTITY_ITEMFRAME_BREAK, + ENTITY_ITEMFRAME_PLACE, + ENTITY_ITEMFRAME_REMOVE_ITEM, + ENTITY_ITEMFRAME_ROTATE_ITEM, + ENTITY_ITEM_BREAK, + ENTITY_ITEM_PICKUP, + ENTITY_LEASHKNOT_BREAK, + ENTITY_LEASHKNOT_PLACE, + ENTITY_LIGHTNING_IMPACT, + ENTITY_LIGHTNING_THUNDER, + ENTITY_LINGERINGPOTION_THROW, + ENTITY_LLAMA_AMBIENT, + ENTITY_LLAMA_ANGRY, + ENTITY_LLAMA_CHEST, + ENTITY_LLAMA_DEATH, + ENTITY_LLAMA_EAT, + ENTITY_LLAMA_HURT, + ENTITY_LLAMA_SPIT, + ENTITY_LLAMA_STEP, + ENTITY_LLAMA_SWAG, + ENTITY_MAGMACUBE_DEATH, + ENTITY_MAGMACUBE_HURT, + ENTITY_MAGMACUBE_JUMP, + ENTITY_MAGMACUBE_SQUISH, + ENTITY_MINECART_INSIDE, + ENTITY_MINECART_RIDING, + ENTITY_MOOSHROOM_SHEAR, + ENTITY_MULE_AMBIENT, + ENTITY_MULE_CHEST, + ENTITY_MULE_DEATH, + ENTITY_MULE_HURT, + ENTITY_PAINTING_BREAK, + ENTITY_PAINTING_PLACE, + ENTITY_PARROT_AMBIENT, + ENTITY_PARROT_DEATH, + ENTITY_PARROT_EAT, + ENTITY_PARROT_FLY, + ENTITY_PARROT_HURT, + ENTITY_PARROT_IMITATE_BLAZE, + ENTITY_PARROT_IMITATE_CREEPER, + ENTITY_PARROT_IMITATE_ELDER_GUARDIAN, + ENTITY_PARROT_IMITATE_ENDERDRAGON, + ENTITY_PARROT_IMITATE_ENDERMAN, + ENTITY_PARROT_IMITATE_ENDERMITE, + ENTITY_PARROT_IMITATE_EVOCATION_ILLAGER, + ENTITY_PARROT_IMITATE_GHAST, + ENTITY_PARROT_IMITATE_HUSK, + ENTITY_PARROT_IMITATE_ILLUSION_ILLAGER, + ENTITY_PARROT_IMITATE_MAGMACUBE, + ENTITY_PARROT_IMITATE_POLAR_BEAR, + ENTITY_PARROT_IMITATE_SHULKER, + ENTITY_PARROT_IMITATE_SILVERFISH, + ENTITY_PARROT_IMITATE_SKELETON, + ENTITY_PARROT_IMITATE_SLIME, + ENTITY_PARROT_IMITATE_SPIDER, + ENTITY_PARROT_IMITATE_STRAY, + ENTITY_PARROT_IMITATE_VEX, + ENTITY_PARROT_IMITATE_VINDICATION_ILLAGER, + ENTITY_PARROT_IMITATE_WITCH, + ENTITY_PARROT_IMITATE_WITHER, + ENTITY_PARROT_IMITATE_WITHER_SKELETON, + ENTITY_PARROT_IMITATE_WOLF, + ENTITY_PARROT_IMITATE_ZOMBIE, + ENTITY_PARROT_IMITATE_ZOMBIE_PIGMAN, + ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER, + ENTITY_PARROT_STEP, + ENTITY_PIG_AMBIENT, + ENTITY_PIG_DEATH, + ENTITY_PIG_HURT, + ENTITY_PIG_SADDLE, + ENTITY_PIG_STEP, + ENTITY_PLAYER_ATTACK_CRIT, + ENTITY_PLAYER_ATTACK_KNOCKBACK, + ENTITY_PLAYER_ATTACK_NODAMAGE, + ENTITY_PLAYER_ATTACK_STRONG, + ENTITY_PLAYER_ATTACK_SWEEP, + ENTITY_PLAYER_ATTACK_WEAK, + ENTITY_PLAYER_BIG_FALL, + ENTITY_PLAYER_BREATH, + ENTITY_PLAYER_BURP, + ENTITY_PLAYER_DEATH, + ENTITY_PLAYER_HURT, + ENTITY_PLAYER_HURT_DROWN, + ENTITY_PLAYER_HURT_ON_FIRE, + ENTITY_PLAYER_LEVELUP, + ENTITY_PLAYER_SMALL_FALL, + ENTITY_PLAYER_SPLASH, + ENTITY_PLAYER_SWIM, + ENTITY_POLAR_BEAR_AMBIENT, + ENTITY_POLAR_BEAR_BABY_AMBIENT, + ENTITY_POLAR_BEAR_DEATH, + ENTITY_POLAR_BEAR_HURT, + ENTITY_POLAR_BEAR_STEP, + ENTITY_POLAR_BEAR_WARNING, + ENTITY_RABBIT_AMBIENT, + ENTITY_RABBIT_ATTACK, + ENTITY_RABBIT_DEATH, + ENTITY_RABBIT_HURT, + ENTITY_RABBIT_JUMP, + ENTITY_SHEEP_AMBIENT, + ENTITY_SHEEP_DEATH, + ENTITY_SHEEP_HURT, + ENTITY_SHEEP_SHEAR, + ENTITY_SHEEP_STEP, + ENTITY_SHULKER_AMBIENT, + ENTITY_SHULKER_BULLET_HIT, + ENTITY_SHULKER_BULLET_HURT, + ENTITY_SHULKER_CLOSE, + ENTITY_SHULKER_DEATH, + ENTITY_SHULKER_HURT, + ENTITY_SHULKER_HURT_CLOSED, + ENTITY_SHULKER_OPEN, + ENTITY_SHULKER_SHOOT, + ENTITY_SHULKER_TELEPORT, + ENTITY_SILVERFISH_AMBIENT, + ENTITY_SILVERFISH_DEATH, + ENTITY_SILVERFISH_HURT, + ENTITY_SILVERFISH_STEP, + ENTITY_SKELETON_AMBIENT, + ENTITY_SKELETON_DEATH, + ENTITY_SKELETON_HORSE_AMBIENT, + ENTITY_SKELETON_HORSE_DEATH, + ENTITY_SKELETON_HORSE_HURT, + ENTITY_SKELETON_HURT, + ENTITY_SKELETON_SHOOT, + ENTITY_SKELETON_STEP, + ENTITY_SLIME_ATTACK, + ENTITY_SLIME_DEATH, + ENTITY_SLIME_HURT, + ENTITY_SLIME_JUMP, + ENTITY_SLIME_SQUISH, + ENTITY_SMALL_MAGMACUBE_DEATH, + ENTITY_SMALL_MAGMACUBE_HURT, + ENTITY_SMALL_MAGMACUBE_SQUISH, + ENTITY_SMALL_SLIME_DEATH, + ENTITY_SMALL_SLIME_HURT, + ENTITY_SMALL_SLIME_JUMP, + ENTITY_SMALL_SLIME_SQUISH, + ENTITY_SNOWBALL_THROW, + ENTITY_SNOWMAN_AMBIENT, + ENTITY_SNOWMAN_DEATH, + ENTITY_SNOWMAN_HURT, + ENTITY_SNOWMAN_SHOOT, + ENTITY_SPIDER_AMBIENT, + ENTITY_SPIDER_DEATH, + ENTITY_SPIDER_HURT, + ENTITY_SPIDER_STEP, + ENTITY_SPLASH_POTION_BREAK, + ENTITY_SPLASH_POTION_THROW, + ENTITY_SQUID_AMBIENT, + ENTITY_SQUID_DEATH, + ENTITY_SQUID_HURT, + ENTITY_STRAY_AMBIENT, + ENTITY_STRAY_DEATH, + ENTITY_STRAY_HURT, + ENTITY_STRAY_STEP, + ENTITY_TNT_PRIMED, + ENTITY_VEX_AMBIENT, + ENTITY_VEX_CHARGE, + ENTITY_VEX_DEATH, + ENTITY_VEX_HURT, + ENTITY_VILLAGER_AMBIENT, + ENTITY_VILLAGER_DEATH, + ENTITY_VILLAGER_HURT, + ENTITY_VILLAGER_NO, + ENTITY_VILLAGER_TRADING, + ENTITY_VILLAGER_YES, + ENTITY_VINDICATION_ILLAGER_AMBIENT, + ENTITY_VINDICATION_ILLAGER_DEATH, + ENTITY_VINDICATION_ILLAGER_HURT, + ENTITY_WITCH_AMBIENT, + ENTITY_WITCH_DEATH, + ENTITY_WITCH_DRINK, + ENTITY_WITCH_HURT, + ENTITY_WITCH_THROW, + ENTITY_WITHER_AMBIENT, + ENTITY_WITHER_BREAK_BLOCK, + ENTITY_WITHER_DEATH, + ENTITY_WITHER_HURT, + ENTITY_WITHER_SHOOT, + ENTITY_WITHER_SKELETON_AMBIENT, + ENTITY_WITHER_SKELETON_DEATH, + ENTITY_WITHER_SKELETON_HURT, + ENTITY_WITHER_SKELETON_STEP, + ENTITY_WITHER_SPAWN, + ENTITY_WOLF_AMBIENT, + ENTITY_WOLF_DEATH, + ENTITY_WOLF_GROWL, + ENTITY_WOLF_HOWL, + ENTITY_WOLF_HURT, + ENTITY_WOLF_PANT, + ENTITY_WOLF_SHAKE, + ENTITY_WOLF_STEP, + ENTITY_WOLF_WHINE, + ENTITY_ZOMBIE_AMBIENT, + ENTITY_ZOMBIE_ATTACK_DOOR_WOOD, + ENTITY_ZOMBIE_ATTACK_IRON_DOOR, + ENTITY_ZOMBIE_BREAK_DOOR_WOOD, + ENTITY_ZOMBIE_DEATH, + ENTITY_ZOMBIE_HORSE_AMBIENT, + ENTITY_ZOMBIE_HORSE_DEATH, + ENTITY_ZOMBIE_HORSE_HURT, + ENTITY_ZOMBIE_HURT, + ENTITY_ZOMBIE_INFECT, + ENTITY_ZOMBIE_PIG_AMBIENT, + ENTITY_ZOMBIE_PIG_ANGRY, + ENTITY_ZOMBIE_PIG_DEATH, + ENTITY_ZOMBIE_PIG_HURT, + ENTITY_ZOMBIE_STEP, + ENTITY_ZOMBIE_VILLAGER_AMBIENT, + ENTITY_ZOMBIE_VILLAGER_CONVERTED, + ENTITY_ZOMBIE_VILLAGER_CURE, + ENTITY_ZOMBIE_VILLAGER_DEATH, + ENTITY_ZOMBIE_VILLAGER_HURT, + ENTITY_ZOMBIE_VILLAGER_STEP, + ITEM_ARMOR_EQUIP_CHAIN, + ITEM_ARMOR_EQUIP_DIAMOND, + ITEM_ARMOR_EQUIP_ELYTRA, + ITEM_ARMOR_EQUIP_GENERIC, + ITEM_ARMOR_EQUIP_GOLD, + ITEM_ARMOR_EQUIP_IRON, + ITEM_ARMOR_EQUIP_LEATHER, + ITEM_BOTTLE_EMPTY, + ITEM_BOTTLE_FILL, + ITEM_BOTTLE_FILL_DRAGONBREATH, + ITEM_BUCKET_EMPTY, + ITEM_BUCKET_EMPTY_LAVA, + ITEM_BUCKET_FILL, + ITEM_BUCKET_FILL_LAVA, + ITEM_CHORUS_FRUIT_TELEPORT, + ITEM_ELYTRA_FLYING, + ITEM_FIRECHARGE_USE, + ITEM_FLINTANDSTEEL_USE, + ITEM_HOE_TILL, + ITEM_SHIELD_BLOCK, + ITEM_SHIELD_BREAK, + ITEM_SHOVEL_FLATTEN, + ITEM_TOTEM_USE, + MUSIC_CREATIVE, + MUSIC_CREDITS, + MUSIC_DRAGON, + MUSIC_END, + MUSIC_GAME, + MUSIC_MENU, + MUSIC_NETHER, + RECORD_11, + RECORD_13, + RECORD_BLOCKS, + RECORD_CAT, + RECORD_CHIRP, + RECORD_FAR, + RECORD_MALL, + RECORD_MELLOHI, + RECORD_STAL, + RECORD_STRAD, + RECORD_WAIT, + RECORD_WARD, + UI_BUTTON_CLICK, + UI_TOAST_CHALLENGE_COMPLETE, + UI_TOAST_IN, + UI_TOAST_OUT, + WEATHER_RAIN, + WEATHER_RAIN_ABOVE; +} diff --git a/src/main/java/org/bukkit/SoundCategory.java b/src/main/java/org/bukkit/SoundCategory.java new file mode 100644 index 00000000..ac5e263d --- /dev/null +++ b/src/main/java/org/bukkit/SoundCategory.java @@ -0,0 +1,18 @@ +package org.bukkit; + +/** + * An Enum of categories for sounds. + */ +public enum SoundCategory { + + MASTER, + MUSIC, + RECORDS, + WEATHER, + BLOCKS, + HOSTILE, + NEUTRAL, + PLAYERS, + AMBIENT, + VOICE; +} diff --git a/src/main/java/org/bukkit/Statistic.java b/src/main/java/org/bukkit/Statistic.java new file mode 100644 index 00000000..1472d9a6 --- /dev/null +++ b/src/main/java/org/bukkit/Statistic.java @@ -0,0 +1,136 @@ +package org.bukkit; + +/** + * Represents a countable statistic, which is tracked by the server. + */ +public enum Statistic { + DAMAGE_DEALT, + DAMAGE_TAKEN, + DEATHS, + MOB_KILLS, + PLAYER_KILLS, + FISH_CAUGHT, + ANIMALS_BRED, + LEAVE_GAME, + JUMP, + DROP(Type.ITEM), + PICKUP(Type.ITEM), + PLAY_ONE_TICK, + WALK_ONE_CM, + SWIM_ONE_CM, + FALL_ONE_CM, + SNEAK_TIME, + CLIMB_ONE_CM, + FLY_ONE_CM, + DIVE_ONE_CM, + MINECART_ONE_CM, + BOAT_ONE_CM, + PIG_ONE_CM, + HORSE_ONE_CM, + SPRINT_ONE_CM, + CROUCH_ONE_CM, + AVIATE_ONE_CM, + MINE_BLOCK(Type.BLOCK), + USE_ITEM(Type.ITEM), + BREAK_ITEM(Type.ITEM), + CRAFT_ITEM(Type.ITEM), + KILL_ENTITY(Type.ENTITY), + ENTITY_KILLED_BY(Type.ENTITY), + TIME_SINCE_DEATH, + TALKED_TO_VILLAGER, + TRADED_WITH_VILLAGER, + CAKE_SLICES_EATEN, + CAULDRON_FILLED, + CAULDRON_USED, + ARMOR_CLEANED, + BANNER_CLEANED, + BREWINGSTAND_INTERACTION, + BEACON_INTERACTION, + DROPPER_INSPECTED, + HOPPER_INSPECTED, + DISPENSER_INSPECTED, + NOTEBLOCK_PLAYED, + NOTEBLOCK_TUNED, + FLOWER_POTTED, + TRAPPED_CHEST_TRIGGERED, + ENDERCHEST_OPENED, + ITEM_ENCHANTED, + RECORD_PLAYED, + FURNACE_INTERACTION, + CRAFTING_TABLE_INTERACTION, + CHEST_OPENED, + SLEEP_IN_BED, + SHULKER_BOX_OPENED; + + private final Type type; + + private Statistic() { + this(Type.UNTYPED); + } + + private Statistic(Type type) { + this.type = type; + } + + /** + * Gets the type of this statistic. + * + * @return the type of this statistic + */ + public Type getType() { + return type; + } + + /** + * Checks if this is a substatistic. + *

+ * A substatistic exists en masse for each block, item, or entitytype, depending on + * {@link #getType()}. + *

+ * This is a redundant method and equivalent to checking + * getType() != Type.UNTYPED + * + * @return true if this is a substatistic + */ + public boolean isSubstatistic() { + return type != Type.UNTYPED; + } + + /** + * Checks if this is a substatistic dealing with blocks. + *

+ * This is a redundant method and equivalent to checking + * getType() == Type.BLOCK + * + * @return true if this deals with blocks + */ + public boolean isBlock() { + return type == Type.BLOCK; + } + + /** + * The type of statistic. + * + */ + public enum Type { + /** + * Statistics of this type do not require a qualifier. + */ + UNTYPED, + + /** + * Statistics of this type require an Item Material qualifier. + */ + ITEM, + + /** + * Statistics of this type require a Block Material qualifier. + */ + BLOCK, + + /** + * Statistics of this type require an EntityType qualifier. + */ + ENTITY; + } +} diff --git a/src/main/java/org/bukkit/TravelAgent.java b/src/main/java/org/bukkit/TravelAgent.java new file mode 100644 index 00000000..2dfeffa8 --- /dev/null +++ b/src/main/java/org/bukkit/TravelAgent.java @@ -0,0 +1,94 @@ +package org.bukkit; + +/** + * The Travel Agent handles the creation and the research of Nether and End + * portals when Entities try to use one. + *

+ * It is used in {@link org.bukkit.event.entity.EntityPortalEvent} and in + * {@link org.bukkit.event.player.PlayerPortalEvent} to help developers + * reproduce and/or modify Vanilla behaviour. + */ +public interface TravelAgent { + + /** + * Set the Block radius to search in for available portals. + * + * @param radius the radius in which to search for a portal from the + * location + * @return this travel agent + */ + public TravelAgent setSearchRadius(int radius); + + /** + * Gets the search radius value for finding an available portal. + * + * @return the currently set search radius + */ + public int getSearchRadius(); + + /** + * Sets the maximum radius from the given location to create a portal. + * + * @param radius the radius in which to create a portal from the location + * @return this travel agent + */ + public TravelAgent setCreationRadius(int radius); + + /** + * Gets the maximum radius from the given location to create a portal. + * + * @return the currently set creation radius + */ + public int getCreationRadius(); + + /** + * Returns whether the TravelAgent will attempt to create a destination + * portal or not. + * + * @return whether the TravelAgent should create a destination portal or + * not + */ + public boolean getCanCreatePortal(); + + /** + * Sets whether the TravelAgent should attempt to create a destination + * portal or not. + * + * @param create Sets whether the TravelAgent should create a destination + * portal or not + */ + public void setCanCreatePortal(boolean create); + + /** + * Attempt to find a portal near the given location, if a portal is not + * found it will attempt to create one. + * + * @param location the location where the search for a portal should begin + * @return the location of a portal which has been found or returns the + * location passed to the method if unsuccessful + * @see #createPortal(Location) + */ + public Location findOrCreate(Location location); + + /** + * Attempt to find a portal near the given location. + * + * @param location the desired location of the portal + * @return the location of the nearest portal to the location + */ + public Location findPortal(Location location); + + /** + * Attempt to create a portal near the given location. + *

+ * In the case of a Nether portal teleportation, this will attempt to + * create a Nether portal. + *

+ * In the case of an Ender portal teleportation, this will (re-)create the + * obsidian platform and clean blocks above it. + * + * @param location the desired location of the portal + * @return true if a portal was successfully created + */ + public boolean createPortal(Location location); +} diff --git a/src/main/java/org/bukkit/TreeSpecies.java b/src/main/java/org/bukkit/TreeSpecies.java new file mode 100644 index 00000000..f29062ac --- /dev/null +++ b/src/main/java/org/bukkit/TreeSpecies.java @@ -0,0 +1,74 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents the different species of trees regardless of size. + */ +public enum TreeSpecies { + + /** + * Represents the common tree species. + */ + GENERIC(0x0), + /** + * Represents the darker barked/leaved tree species. + */ + REDWOOD(0x1), + /** + * Represents birches. + */ + BIRCH(0x2), + /** + * Represents jungle trees. + */ + JUNGLE(0x3), + /** + * Represents acacia trees. + */ + ACACIA(0x4), + /** + * Represents dark oak trees. + */ + DARK_OAK(0x5), + ; + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private TreeSpecies(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this species + * + * @return A byte containing the data value of this tree species + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the TreeSpecies with the given data value + * + * @param data Data value to fetch + * @return The {@link TreeSpecies} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static TreeSpecies getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (TreeSpecies species : values()) { + BY_DATA.put(species.data, species); + } + } +} diff --git a/src/main/java/org/bukkit/TreeType.java b/src/main/java/org/bukkit/TreeType.java new file mode 100644 index 00000000..dba084fa --- /dev/null +++ b/src/main/java/org/bukkit/TreeType.java @@ -0,0 +1,76 @@ +package org.bukkit; + +/** + * Tree and organic structure types. + */ +public enum TreeType { + + /** + * Regular tree, no branches + */ + TREE, + /** + * Regular tree, extra tall with branches + */ + BIG_TREE, + /** + * Redwood tree, shaped like a pine tree + */ + REDWOOD, + /** + * Tall redwood tree with just a few leaves at the top + */ + TALL_REDWOOD, + /** + * Birch tree + */ + BIRCH, + /** + * Standard jungle tree; 4 blocks wide and tall + */ + JUNGLE, + /** + * Smaller jungle tree; 1 block wide + */ + SMALL_JUNGLE, + /** + * Jungle tree with cocoa plants; 1 block wide + */ + COCOA_TREE, + /** + * Small bush that grows in the jungle + */ + JUNGLE_BUSH, + /** + * Big red mushroom; short and fat + */ + RED_MUSHROOM, + /** + * Big brown mushroom; tall and umbrella-like + */ + BROWN_MUSHROOM, + /** + * Swamp tree (regular with vines on the side) + */ + SWAMP, + /** + * Acacia tree. + */ + ACACIA, + /** + * Dark Oak tree. + */ + DARK_OAK, + /** + * Mega redwood tree; 4 blocks wide and tall + */ + MEGA_REDWOOD, + /** + * Tall birch tree + */ + TALL_BIRCH, + /** + * Large plant native to The End + */ + CHORUS_PLANT, +} diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java new file mode 100644 index 00000000..89ef6c40 --- /dev/null +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -0,0 +1,62 @@ +package org.bukkit; + +import java.util.List; + +import org.bukkit.advancement.Advancement; +import org.bukkit.inventory.ItemStack; + +/** + * This interface provides value conversions that may be specific to a + * runtime, or have arbitrary meaning (read: magic values). + *

+ * Their existence and behavior is not guaranteed across future versions. They + * may be poorly named, throw exceptions, have misleading parameters, or any + * other bad programming practice. + */ +@Deprecated +public interface UnsafeValues { + + Material getMaterialFromInternalName(String name); + + List tabCompleteInternalMaterialName(String token, List completions); + + ItemStack modifyItemStack(ItemStack stack, String arguments); + + Statistic getStatisticFromInternalName(String name); + + Achievement getAchievementFromInternalName(String name); + + List tabCompleteInternalStatisticOrAchievementName(String token, List completions); + + /** + * Load an advancement represented by the specified string into the server. + * The advancement format is governed by Minecraft and has no specified + * layout. + *
+ * It is currently a JSON object, as described by the Minecraft Wiki: + * http://minecraft.gamepedia.com/Advancements + *
+ * Loaded advancements will be stored and persisted across server restarts + * and reloads. + *
+ * Callers should be prepared for {@link Exception} to be thrown. + * + * @param key the unique advancement key + * @param advancement representation of the advancement + * @return the loaded advancement or null if an error occurred + */ + Advancement loadAdvancement(NamespacedKey key, String advancement); + + /** + * Delete an advancement which was loaded and saved by + * {@link #loadAdvancement(NamespacedKey, String)}. + *
+ * This method will only remove advancement from persistent storage. It + * should be accompanied by a call to {@link Server#reloadData()} in order + * to fully remove it from the running instance. + * + * @param key the unique advancement key + * @return true if a file matching this key was found and deleted + */ + boolean removeAdvancement(NamespacedKey key); +} diff --git a/src/main/java/org/bukkit/Utility.java b/src/main/java/org/bukkit/Utility.java new file mode 100644 index 00000000..da66853e --- /dev/null +++ b/src/main/java/org/bukkit/Utility.java @@ -0,0 +1,18 @@ +package org.bukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates a method (and sometimes constructor) will chain + * its internal operations. + *

+ * This is solely meant for identifying methods that don't need to be + * overridden / handled manually. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +public @interface Utility { +} diff --git a/src/main/java/org/bukkit/Warning.java b/src/main/java/org/bukkit/Warning.java new file mode 100644 index 00000000..6a2a3b0d --- /dev/null +++ b/src/main/java/org/bukkit/Warning.java @@ -0,0 +1,109 @@ +package org.bukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +/** + * This designates the warning state for a specific item. + *

+ * When the server settings dictate 'default' warnings, warnings are printed + * if the {@link #value()} is true. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Warning { + + /** + * This represents the states that server verbose for warnings may be. + */ + public enum WarningState { + + /** + * Indicates all warnings should be printed for deprecated items. + */ + ON, + /** + * Indicates no warnings should be printed for deprecated items. + */ + OFF, + /** + * Indicates each warning would default to the configured {@link + * Warning} annotation, or always if annotation not found. + */ + DEFAULT; + + private static final Map values = ImmutableMap.builder() + .put("off", OFF) + .put("false", OFF) + .put("f", OFF) + .put("no", OFF) + .put("n", OFF) + .put("on", ON) + .put("true", ON) + .put("t", ON) + .put("yes", ON) + .put("y", ON) + .put("", DEFAULT) + .put("d", DEFAULT) + .put("default", DEFAULT) + .build(); + + /** + * This method checks the provided warning should be printed for this + * state + * + * @param warning The warning annotation added to a deprecated item + * @return

    + *
  • ON is always True + *
  • OFF is always false + *
  • DEFAULT is false if and only if annotation is not null and + * specifies false for {@link Warning#value()}, true otherwise. + *
+ */ + public boolean printFor(Warning warning) { + if (this == DEFAULT) { + return warning == null || warning.value(); + } + return this == ON; + } + + /** + * This method returns the corresponding warning state for the given + * string value. + * + * @param value The string value to check + * @return {@link #DEFAULT} if not found, or the respective + * WarningState + */ + public static WarningState value(final String value) { + if (value == null) { + return DEFAULT; + } + WarningState state = values.get(value.toLowerCase()); + if (state == null) { + return DEFAULT; + } + return state; + } + } + + /** + * This sets if the deprecation warnings when registering events gets + * printed when the setting is in the default state. + * + * @return false normally, or true to encourage warning printout + */ + boolean value() default false; + + /** + * This can provide detailed information on why the event is deprecated. + * + * @return The reason an event is deprecated + */ + String reason() default ""; +} diff --git a/src/main/java/org/bukkit/WeatherType.java b/src/main/java/org/bukkit/WeatherType.java new file mode 100644 index 00000000..36b993f1 --- /dev/null +++ b/src/main/java/org/bukkit/WeatherType.java @@ -0,0 +1,17 @@ +package org.bukkit; + +/** + * An enum of all current weather types + */ +public enum WeatherType { + + /** + * Raining or snowing depending on biome. + */ + DOWNFALL, + /** + * Clear weather, clouds but no rain. + */ + CLEAR, + ; +} diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java new file mode 100644 index 00000000..dc0e2212 --- /dev/null +++ b/src/main/java/org/bukkit/World.java @@ -0,0 +1,1705 @@ +package org.bukkit; + +import java.io.File; +import org.bukkit.generator.ChunkGenerator; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.entity.*; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.Metadatable; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.util.Consumer; +import org.bukkit.util.Vector; + +/** + * Represents a world, which may contain entities, chunks and blocks + */ +public interface World extends PluginMessageRecipient, Metadatable { + + /** + * Gets the {@link Block} at the given coordinates + * + * @param x X-coordinate of the block + * @param y Y-coordinate of the block + * @param z Z-coordinate of the block + * @return Block at the given coordinates + * @see #getBlockTypeIdAt(int, int, int) Returns the current type ID of + * the block + */ + public Block getBlockAt(int x, int y, int z); + + /** + * Gets the {@link Block} at the given {@link Location} + * + * @param location Location of the block + * @return Block at the given location + * @see #getBlockTypeIdAt(Location) Returns the current type ID + * of the block + */ + public Block getBlockAt(Location location); + + /** + * Gets the block type ID at the given coordinates + * + * @param x X-coordinate of the block + * @param y Y-coordinate of the block + * @param z Z-coordinate of the block + * @return Type ID of the block at the given coordinates + * @see #getBlockAt(int, int, int) Returns a live Block object at the + * given location + * @deprecated Magic value + */ + @Deprecated + public int getBlockTypeIdAt(int x, int y, int z); + + /** + * Gets the block type ID at the given {@link Location} + * + * @param location Location of the block + * @return Type ID of the block at the given location + * @see #getBlockAt(Location) Returns a live Block object at + * the given location + * @deprecated Magic value + */ + @Deprecated + public int getBlockTypeIdAt(Location location); + + /** + * Gets the y coordinate of the lowest block at this position such that the + * block and all blocks above it are transparent for lighting purposes. + * + * @param x X-coordinate of the blocks + * @param z Z-coordinate of the blocks + * @return Y-coordinate of the described block + */ + public int getHighestBlockYAt(int x, int z); + + /** + * Gets the y coordinate of the lowest block at the given {@link Location} + * such that the block and all blocks above it are transparent for lighting + * purposes. + * + * @param location Location of the blocks + * @return Y-coordinate of the highest non-air block + */ + public int getHighestBlockYAt(Location location); + + /** + * Gets the lowest block at the given coordinates such that the block and + * all blocks above it are transparent for lighting purposes. + * + * @param x X-coordinate of the block + * @param z Z-coordinate of the block + * @return Highest non-empty block + */ + public Block getHighestBlockAt(int x, int z); + + /** + * Gets the lowest block at the given {@link Location} such that the block + * and all blocks above it are transparent for lighting purposes. + * + * @param location Coordinates to get the highest block + * @return Highest non-empty block + */ + public Block getHighestBlockAt(Location location); + + /** + * Gets the {@link Chunk} at the given coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Chunk at the given coordinates + */ + public Chunk getChunkAt(int x, int z); + + /** + * Gets the {@link Chunk} at the given {@link Location} + * + * @param location Location of the chunk + * @return Chunk at the given location + */ + public Chunk getChunkAt(Location location); + + /** + * Gets the {@link Chunk} that contains the given {@link Block} + * + * @param block Block to get the containing chunk from + * @return The chunk that contains the given block + */ + public Chunk getChunkAt(Block block); + + /** + * Checks if the specified {@link Chunk} is loaded + * + * @param chunk The chunk to check + * @return true if the chunk is loaded, otherwise false + */ + public boolean isChunkLoaded(Chunk chunk); + + /** + * Gets an array of all loaded {@link Chunk}s + * + * @return Chunk[] containing all loaded chunks + */ + public Chunk[] getLoadedChunks(); + + /** + * Loads the specified {@link Chunk} + * + * @param chunk The chunk to load + */ + public void loadChunk(Chunk chunk); + + /** + * Checks if the {@link Chunk} at the specified coordinates is loaded + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk is loaded, otherwise false + */ + public boolean isChunkLoaded(int x, int z); + + /** + * Checks if the {@link Chunk} at the specified coordinates is loaded and + * in use by one or more players + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk is loaded and in use by one or more players, + * otherwise false + */ + public boolean isChunkInUse(int x, int z); + + /** + * Loads the {@link Chunk} at the specified coordinates + *

+ * If the chunk does not exist, it will be generated. + *

+ * This method is analogous to {@link #loadChunk(int, int, boolean)} where + * generate is true. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + */ + public void loadChunk(int x, int z); + + /** + * Loads the {@link Chunk} at the specified coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param generate Whether or not to generate a chunk if it doesn't + * already exist + * @return true if the chunk has loaded successfully, otherwise false + */ + public boolean loadChunk(int x, int z, boolean generate); + + /** + * Safely unloads and saves the {@link Chunk} at the specified coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where safe and saveis true + * + * @param chunk the chunk to unload + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(Chunk chunk); + + /** + * Safely unloads and saves the {@link Chunk} at the specified coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where safe and saveis true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(int x, int z); + + /** + * Safely unloads and optionally saves the {@link Chunk} at the specified + * coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where save is true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param save Whether or not to save the chunk + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(int x, int z, boolean save); + + /** + * Unloads and optionally saves the {@link Chunk} at the specified + * coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param save Controls whether the chunk is saved + * @param safe Controls whether to unload the chunk when players are + * nearby + * @return true if the chunk has unloaded successfully, otherwise false + * @deprecated it is never safe to remove a chunk in use + */ + @Deprecated + public boolean unloadChunk(int x, int z, boolean save, boolean safe); + + /** + * Safely queues the {@link Chunk} at the specified coordinates for + * unloading + *

+ * This method is analogous to {@link #unloadChunkRequest(int, int, + * boolean)} where safe is true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true is the queue attempt was successful, otherwise false + */ + public boolean unloadChunkRequest(int x, int z); + + /** + * Queues the {@link Chunk} at the specified coordinates for unloading + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param safe Controls whether to queue the chunk when players are nearby + * @return Whether the chunk was actually queued + */ + public boolean unloadChunkRequest(int x, int z, boolean safe); + + /** + * Regenerates the {@link Chunk} at the specified coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Whether the chunk was actually regenerated + */ + public boolean regenerateChunk(int x, int z); + + /** + * Resends the {@link Chunk} to all clients + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Whether the chunk was actually refreshed + * + * @deprecated This method is not guaranteed to work suitably across all client implementations. + */ + @Deprecated + public boolean refreshChunk(int x, int z); + + /** + * Drops an item at the specified {@link Location} + * + * @param location Location to drop the item + * @param item ItemStack to drop + * @return ItemDrop entity created as a result of this method + */ + public Item dropItem(Location location, ItemStack item); + + /** + * Drops an item at the specified {@link Location} with a random offset + * + * @param location Location to drop the item + * @param item ItemStack to drop + * @return ItemDrop entity created as a result of this method + */ + public Item dropItemNaturally(Location location, ItemStack item); + + /** + * Creates an {@link Arrow} entity at the given {@link Location} + * + * @param location Location to spawn the arrow + * @param direction Direction to shoot the arrow in + * @param speed Speed of the arrow. A recommend speed is 0.6 + * @param spread Spread of the arrow. A recommend spread is 12 + * @return Arrow entity spawned as a result of this method + */ + public Arrow spawnArrow(Location location, Vector direction, float speed, float spread); + + /** + * Creates an arrow entity of the given class at the given {@link Location} + * + * @param type of arrow to spawn + * @param location Location to spawn the arrow + * @param direction Direction to shoot the arrow in + * @param speed Speed of the arrow. A recommend speed is 0.6 + * @param spread Spread of the arrow. A recommend spread is 12 + * @param clazz the Entity class for the arrow + * {@link SpectralArrow},{@link Arrow},{@link TippedArrow} + * @return Arrow entity spawned as a result of this method + */ + public T spawnArrow(Location location, Vector direction, float speed, float spread, Class clazz); + + /** + * Creates a tree at the given {@link Location} + * + * @param location Location to spawn the tree + * @param type Type of the tree to create + * @return true if the tree was created successfully, otherwise false + */ + public boolean generateTree(Location location, TreeType type); + + /** + * Creates a tree at the given {@link Location} + * + * @param loc Location to spawn the tree + * @param type Type of the tree to create + * @param delegate A class to call for each block changed as a result of + * this method + * @return true if the tree was created successfully, otherwise false + * @deprecated rarely used API that was largely for implementation purposes + */ + @Deprecated + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate); + + /** + * Creates a entity at the given {@link Location} + * + * @param loc The location to spawn the entity + * @param type The entity to spawn + * @return Resulting Entity of this method, or null if it was unsuccessful + */ + public Entity spawnEntity(Location loc, EntityType type); + + /** + * Strikes lightning at the given {@link Location} + * + * @param loc The location to strike lightning + * @return The lightning entity. + */ + public LightningStrike strikeLightning(Location loc); + + /** + * Strikes lightning at the given {@link Location} without doing damage + * + * @param loc The location to strike lightning + * @return The lightning entity. + */ + public LightningStrike strikeLightningEffect(Location loc); + + /** + * Get a list of all entities in this World + * + * @return A List of all Entities currently residing in this world + */ + public List getEntities(); + + /** + * Get a list of all living entities in this World + * + * @return A List of all LivingEntities currently residing in this world + */ + public List getLivingEntities(); + + /** + * Get a collection of all entities in this World matching the given + * class/interface + * + * @param an entity subclass + * @param classes The classes representing the types of entity to match + * @return A List of all Entities currently residing in this world that + * match the given class/interface + */ + @Deprecated + public Collection getEntitiesByClass(Class... classes); + + /** + * Get a collection of all entities in this World matching the given + * class/interface + * + * @param an entity subclass + * @param cls The class representing the type of entity to match + * @return A List of all Entities currently residing in this world that + * match the given class/interface + */ + public Collection getEntitiesByClass(Class cls); + + /** + * Get a collection of all entities in this World matching any of the + * given classes/interfaces + * + * @param classes The classes representing the types of entity to match + * @return A List of all Entities currently residing in this world that + * match one or more of the given classes/interfaces + */ + public Collection getEntitiesByClasses(Class... classes); + + /** + * Get a list of all players in this World + * + * @return A list of all Players currently residing in this world + */ + public List getPlayers(); + + /** + * Returns a list of entities within a bounding box centered around a Location. + * + * Some implementations may impose artificial restrictions on the size of the search bounding box. + * + * @param location The center of the bounding box + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @return the collection of entities near location. This will always be a non-null collection. + */ + public Collection getNearbyEntities(Location location, double x, double y, double z); + + /** + * Gets the unique name of this world + * + * @return Name of this world + */ + public String getName(); + + /** + * Gets the Unique ID of this world + * + * @return Unique ID of this world. + */ + public UUID getUID(); + + /** + * Gets the default spawn {@link Location} of this world + * + * @return The spawn location of this world + */ + public Location getSpawnLocation(); + + /** + * Sets the spawn location of the world. + *
+ * The location provided must be equal to this world. + * + * @param location The {@link Location} to set the spawn for this world at. + * @return True if it was successfully set. + */ + public boolean setSpawnLocation(Location location); + + /** + * Sets the spawn location of the world + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return True if it was successfully set. + */ + public boolean setSpawnLocation(int x, int y, int z); + + /** + * Gets the relative in-game time of this world. + *

+ * The relative time is analogous to hours * 1000 + * + * @return The current relative time + * @see #getFullTime() Returns an absolute time of this world + */ + public long getTime(); + + /** + * Sets the relative in-game time on the server. + *

+ * The relative time is analogous to hours * 1000 + *

+ * Note that setting the relative time below the current relative time + * will actually move the clock forward a day. If you require to rewind + * time, please see {@link #setFullTime(long)} + * + * @param time The new relative time to set the in-game time to (in + * hours*1000) + * @see #setFullTime(long) Sets the absolute time of this world + */ + public void setTime(long time); + + /** + * Gets the full in-game time on this world + * + * @return The current absolute time + * @see #getTime() Returns a relative time of this world + */ + public long getFullTime(); + + /** + * Sets the in-game time on the server + *

+ * Note that this sets the full time of the world, which may cause adverse + * effects such as breaking redstone clocks and any scheduled events + * + * @param time The new absolute time to set this world to + * @see #setTime(long) Sets the relative time of this world + */ + public void setFullTime(long time); + + /** + * Returns whether the world has an ongoing storm. + * + * @return Whether there is an ongoing storm + */ + public boolean hasStorm(); + + /** + * Set whether there is a storm. A duration will be set for the new + * current conditions. + * + * @param hasStorm Whether there is rain and snow + */ + public void setStorm(boolean hasStorm); + + /** + * Get the remaining time in ticks of the current conditions. + * + * @return Time in ticks + */ + public int getWeatherDuration(); + + /** + * Set the remaining time in ticks of the current conditions. + * + * @param duration Time in ticks + */ + public void setWeatherDuration(int duration); + + /** + * Returns whether there is thunder. + * + * @return Whether there is thunder + */ + public boolean isThundering(); + + /** + * Set whether it is thundering. + * + * @param thundering Whether it is thundering + */ + public void setThundering(boolean thundering); + + /** + * Get the thundering duration. + * + * @return Duration in ticks + */ + public int getThunderDuration(); + + /** + * Set the thundering duration. + * + * @param duration Duration in ticks + */ + public void setThunderDuration(int duration); + + /** + * Creates explosion at given coordinates with given power + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power, boolean setFire); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire or breaking blocks. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks); + + /** + * Creates explosion at given coordinates with given power + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(Location loc, float power); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire. + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + boolean createExplosion(Location loc, float power, boolean setFire); + + // Paper start + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks); + + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * Will destroy other blocks + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Entity source, Location loc, float power, boolean setFire) { + return createExplosion(source, loc, power, setFire, true); + } + /** + * Creates explosion at given location with given power, with the specified entity as the source. + * Will set blocks on fire and destroy blocks. + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Entity source, Location loc, float power) { + return createExplosion(source, loc, power, true, true); + } + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Entity source, float power, boolean setFire, boolean breakBlocks) { + return createExplosion(source, source.getLocation(), power, setFire, breakBlocks); + } + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * Will destroy blocks. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Entity source, float power, boolean setFire) { + return createExplosion(source, source.getLocation(), power, setFire, true); + } + + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Entity source, float power) { + return createExplosion(source, source.getLocation(), power, true, true); + } + + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire or breaking blocks. + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + default boolean createExplosion(Location loc, float power, boolean setFire, boolean breakBlocks) { + return createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks); + } + // Paper end + + /** + * Gets the {@link Environment} type of this world + * + * @return This worlds Environment type + */ + public Environment getEnvironment(); + + /** + * Gets the Seed for this world. + * + * @return This worlds Seed + */ + public long getSeed(); + + /** + * Gets the current PVP setting for this world. + * + * @return True if PVP is enabled + */ + public boolean getPVP(); + + /** + * Sets the PVP setting for this world. + * + * @param pvp True/False whether PVP should be Enabled. + */ + public void setPVP(boolean pvp); + + /** + * Gets the chunk generator for this world + * + * @return ChunkGenerator associated with this world + */ + public ChunkGenerator getGenerator(); + + /** + * Saves world to disk + */ + public void save(); + + /** + * Gets a list of all applied {@link BlockPopulator}s for this World + * + * @return List containing any or none BlockPopulators + */ + public List getPopulators(); + + /** + * Spawn an entity of a specific class at the given {@link Location} + * + * @param location the {@link Location} to spawn the entity at + * @param clazz the class of the {@link Entity} to spawn + * @param the class of the {@link Entity} to spawn + * @return an instance of the spawned {@link Entity} + * @throws IllegalArgumentException if either parameter is null or the + * {@link Entity} requested cannot be spawned + */ + public T spawn(Location location, Class clazz) throws IllegalArgumentException; + + /** + * Spawn an entity of a specific class at the given {@link Location}, with + * the supplied function run before the entity is added to the world. + *
+ * Note that when the function is run, the entity will not be actually in + * the world. Any operation involving such as teleporting the entity is undefined + * until after this function returns. + * + * @param location the {@link Location} to spawn the entity at + * @param clazz the class of the {@link Entity} to spawn + * @param function the function to be run before the entity is spawned. + * @param the class of the {@link Entity} to spawn + * @return an instance of the spawned {@link Entity} + * @throws IllegalArgumentException if either parameter is null or the + * {@link Entity} requested cannot be spawned + */ + public T spawn(Location location, Class clazz, Consumer function) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of + * the specified {@link Material}. The material dictates what is falling. + * When the FallingBlock hits the ground, it will place that block. + *

+ * The Material must be a block type, check with {@link Material#isBlock() + * material.isBlock()}. The Material may not be air. + * + * @param location The {@link Location} to spawn the FallingBlock + * @param data The block data + * @return The spawned {@link FallingBlock} instance + * @throws IllegalArgumentException if {@link Location} or {@link + * MaterialData} are null or {@link Material} of the {@link MaterialData} is not a block + */ + public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of + * the specified {@link Material}. The material dictates what is falling. + * When the FallingBlock hits the ground, it will place that block. + *

+ * The Material must be a block type, check with {@link Material#isBlock() + * material.isBlock()}. The Material may not be air. + * + * @param location The {@link Location} to spawn the FallingBlock + * @param material The block {@link Material} type + * @param data The block data + * @return The spawned {@link FallingBlock} instance + * @throws IllegalArgumentException if {@link Location} or {@link + * Material} are null or {@link Material} is not a block + * @deprecated Magic value + */ + @Deprecated + public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of + * the specified blockId (converted to {@link Material}) + * + * @param location The {@link Location} to spawn the FallingBlock + * @param blockId The id of the intended material + * @param blockData The block data + * @return The spawned FallingBlock instance + * @throws IllegalArgumentException if location is null, or blockId is + * invalid + * @see #spawnFallingBlock(Location, Material, byte) + * @deprecated Magic value + */ + @Deprecated + public FallingBlock spawnFallingBlock(Location location, int blockId, byte blockData) throws IllegalArgumentException; + + /** + * Plays an effect to all players within a default radius around a given + * location. + * + * @param location the {@link Location} around which players must be to + * hear the sound + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(Location location, Effect effect, int data); + + /** + * Plays an effect to all players within a given radius around a location. + * + * @param location the {@link Location} around which players must be to + * hear the effect + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @param radius the radius around the location + */ + public void playEffect(Location location, Effect effect, int data, int radius); + + /** + * Plays an effect to all players within a default radius around a given + * location. + * + * @param data dependant on the type of effect + * @param location the {@link Location} around which players must be to + * hear the sound + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(Location location, Effect effect, T data); + + /** + * Plays an effect to all players within a given radius around a location. + * + * @param data dependant on the type of effect + * @param location the {@link Location} around which players must be to + * hear the effect + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @param radius the radius around the location + */ + public void playEffect(Location location, Effect effect, T data, int radius); + + /** + * Get empty chunk snapshot (equivalent to all air blocks), optionally + * including valid biome data. Used for representing an ungenerated chunk, + * or for fetching only biome data without loading a chunk. + * + * @param x - chunk x coordinate + * @param z - chunk z coordinate + * @param includeBiome - if true, snapshot includes per-coordinate biome + * type + * @param includeBiomeTempRain - if true, snapshot includes per-coordinate + * raw biome temperature and rainfall + * @return The empty snapshot. + */ + public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTempRain); + + /** + * Sets the spawn flags for this. + * + * @param allowMonsters - if true, monsters are allowed to spawn in this + * world. + * @param allowAnimals - if true, animals are allowed to spawn in this + * world. + */ + public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals); + + /** + * Gets whether animals can spawn in this world. + * + * @return whether animals can spawn in this world. + */ + public boolean getAllowAnimals(); + + /** + * Gets whether monsters can spawn in this world. + * + * @return whether monsters can spawn in this world. + */ + public boolean getAllowMonsters(); + + /** + * Gets the biome for the given block coordinates. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Biome of the requested block + */ + Biome getBiome(int x, int z); + + /** + * Sets the biome for the given block coordinates + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @param bio new Biome type for this block + */ + void setBiome(int x, int z, Biome bio); + + /** + * Gets the temperature for the given block coordinates. + *

+ * It is safe to run this method when the block does not exist, it will + * not create the block. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Temperature of the requested block + */ + public double getTemperature(int x, int z); + + /** + * Gets the humidity for the given block coordinates. + *

+ * It is safe to run this method when the block does not exist, it will + * not create the block. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Humidity of the requested block + */ + public double getHumidity(int x, int z); + + /** + * Gets the maximum height of this world. + *

+ * If the max height is 100, there are only blocks from y=0 to y=99. + * + * @return Maximum height of the world + */ + public int getMaxHeight(); + + /** + * Gets the sea level for this world. + *

+ * This is often half of {@link #getMaxHeight()} + * + * @return Sea level + */ + public int getSeaLevel(); + + /** + * Gets whether the world's spawn area should be kept loaded into memory + * or not. + * + * @return true if the world's spawn area will be kept loaded into memory. + */ + public boolean getKeepSpawnInMemory(); + + /** + * Sets whether the world's spawn area should be kept loaded into memory + * or not. + * + * @param keepLoaded if true then the world's spawn area will be kept + * loaded into memory. + */ + public void setKeepSpawnInMemory(boolean keepLoaded); + + /** + * Gets whether or not the world will automatically save + * + * @return true if the world will automatically save, otherwise false + */ + public boolean isAutoSave(); + + /** + * Sets whether or not the world will automatically save + * + * @param value true if the world should automatically save, otherwise + * false + */ + public void setAutoSave(boolean value); + + /** + * Sets the Difficulty of the world. + * + * @param difficulty the new difficulty you want to set the world to + */ + public void setDifficulty(Difficulty difficulty); + + /** + * Gets the Difficulty of the world. + * + * @return The difficulty of the world. + */ + public Difficulty getDifficulty(); + + /** + * Gets the folder of this world on disk. + * + * @return The folder of this world. + */ + public File getWorldFolder(); + + /** + * Gets the type of this world. + * + * @return Type of this world. + */ + public WorldType getWorldType(); + + /** + * Gets whether or not structures are being generated. + * + * @return True if structures are being generated. + */ + public boolean canGenerateStructures(); + + /** + * Gets the world's ticks per animal spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn animals. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn animals in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn animals + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, animal spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 400. + * + * @return The world's ticks per animal spawns value + */ + public long getTicksPerAnimalSpawns(); + + /** + * Sets the world's ticks per animal spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn animals. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn animals in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn animals + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, animal spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 400. + * + * @param ticksPerAnimalSpawns the ticks per animal spawns value you want + * to set the world to + */ + public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns); + + /** + * Gets the world's ticks per monster spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn monsters. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, monsters spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 1. + * + * @return The world's ticks per monster spawns value + */ + public long getTicksPerMonsterSpawns(); + + /** + * Sets the world's ticks per monster spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn monsters. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters in + * this world on every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, monsters spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 1. + * + * @param ticksPerMonsterSpawns the ticks per monster spawns value you + * want to set the world to + */ + public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns); + + /** + * Gets limit for number of monsters that can spawn in a chunk in this + * world + * + * @return The monster spawn limit + */ + int getMonsterSpawnLimit(); + + /** + * Sets the limit for number of monsters that can spawn in a chunk in this + * world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setMonsterSpawnLimit(int limit); + + /** + * Gets the limit for number of animals that can spawn in a chunk in this + * world + * + * @return The animal spawn limit + */ + int getAnimalSpawnLimit(); + + /** + * Sets the limit for number of animals that can spawn in a chunk in this + * world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setAnimalSpawnLimit(int limit); + + /** + * Gets the limit for number of water animals that can spawn in a chunk in + * this world + * + * @return The water animal spawn limit + */ + int getWaterAnimalSpawnLimit(); + + /** + * Sets the limit for number of water animals that can spawn in a chunk in + * this world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setWaterAnimalSpawnLimit(int limit); + + /** + * Gets the limit for number of ambient mobs that can spawn in a chunk in + * this world + * + * @return The ambient spawn limit + */ + int getAmbientSpawnLimit(); + + /** + * Sets the limit for number of ambient mobs that can spawn in a chunk in + * this world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setAmbientSpawnLimit(int limit); + + /** + * Play a Sound at the provided Location in the World + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + void playSound(Location location, Sound sound, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. No + * sound will be heard by the players if their clients do not have the + * respective sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + void playSound(Location location, String sound, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param category the category of the sound + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. No sound + * will be heard by the players if their clients do not have the respective + * sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param category the category of the sound + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + void playSound(Location location, String sound, SoundCategory category, float volume, float pitch); + + /** + * Get existing rules + * + * @return An array of rules + */ + public String[] getGameRules(); + + /** + * Gets the current state of the specified rule + *

+ * Will return null if rule passed is null + * + * @param rule Rule to look up value of + * @return String value of rule + */ + public String getGameRuleValue(String rule); + + /** + * Set the specified gamerule to specified value. + *

+ * The rule may attempt to validate the value passed, will return true if + * value was set. + *

+ * If rule is null, the function will return false. + * + * @param rule Rule to set + * @param value Value to set rule to + * @return True if rule was set + */ + public boolean setGameRuleValue(String rule, String value); + + /** + * Checks if string is a valid game rule + * + * @param rule Rule to check + * @return True if rule exists + */ + public boolean isGameRule(String rule); + + /** + * Gets the world border for this world. + * + * @return The world border for this world. + */ + public WorldBorder getWorldBorder(); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + */ + public void spawnParticle(Particle particle, Location location, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, T data); + + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data); + + // Spigot start + public class Spigot + { + + /** + * Plays an effect to all players within a default radius around a given + * location. + * + * @param location the {@link Location} around which players must be to + * see the effect + * @param effect the {@link Effect} + * @throws IllegalArgumentException if the location or effect is null. + * It also throws when the effect requires a material or a material data + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + public void playEffect(Location location, Effect effect) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Plays an effect to all players within a default radius around a given + * location. The effect will use the provided material (and material + * data if required). The particle's position on the client will be the + * given location, adjusted on each axis by a normal distribution with + * mean 0 and standard deviation given in the offset parameters, each + * particle has independently calculated offsets. The effect will have + * the given speed and particle count if the effect is a particle. Some + * effect will create multiple particles. + * + * @param location the {@link Location} around which players must be to + * see the effect + * @param effect effect the {@link Effect} + * @param id the item/block/data id for the effect + * @param data the data value of the block/item for the effect + * @param offsetX the amount to be randomly offset by in the X axis + * @param offsetY the amount to be randomly offset by in the Y axis + * @param offsetZ the amount to be randomly offset by in the Z axis + * @param speed the speed of the particles + * @param particleCount the number of particles + * @param radius the radius around the location + * @deprecated Spigot specific API, use {@link Particle}. + */ + @Deprecated + public void playEffect(Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Strikes lightning at the given {@link Location} and possibly without sound + * + * @param loc The location to strike lightning + * @param isSilent Whether this strike makes no sound + * @return The lightning entity. + */ + public LightningStrike strikeLightning(Location loc, boolean isSilent) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Strikes lightning at the given {@link Location} without doing damage and possibly without sound + * + * @param loc The location to strike lightning + * @param isSilent Whether this strike makes no sound + * @return The lightning entity. + */ + public LightningStrike strikeLightningEffect(Location loc, boolean isSilent) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + Spigot spigot(); + // Spigot end + + /** + * Represents various map environment types that a world may be + */ + public enum Environment { + + /** + * Represents the "normal"/"surface world" map + */ + NORMAL(0), + /** + * Represents a nether based map ("hell") + */ + NETHER(-1), + /** + * Represents the "end" map + */ + THE_END(1); + + private final int id; + private static final Map lookup = new HashMap(); + + private Environment(int id) { + this.id = id; + } + + public static void registerEnvironment(Environment env){ + lookup.put(env.getId(), env); + } + + /** + * Gets the dimension ID of this environment + * + * @return dimension ID + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Get an environment by ID + * + * @param id The ID of the environment + * @return The environment + * @deprecated Magic value + */ + @Deprecated + public static Environment getEnvironment(int id) { + return lookup.get(id); + } + + static { + for (Environment env : values()) { + lookup.put(env.getId(), env); + } + } + } +} diff --git a/src/main/java/org/bukkit/WorldBorder.java b/src/main/java/org/bukkit/WorldBorder.java new file mode 100644 index 00000000..4dc18edc --- /dev/null +++ b/src/main/java/org/bukkit/WorldBorder.java @@ -0,0 +1,117 @@ +package org.bukkit; + +public interface WorldBorder { + + /** + * Resets the border to default values. + */ + public void reset(); + + /** + * Gets the current side length of the border. + * + * @return The current side length of the border. + */ + public double getSize(); + + /** + * Sets the border to a square region with the specified side length in blocks. + * + * @param newSize The new size of the border. + */ + public void setSize(double newSize); + + /** + * Sets the border to a square region with the specified side length in blocks. + * + * @param newSize The new side length of the border. + * @param seconds The time in seconds in which the border grows or shrinks from the previous size to that being set. + */ + public void setSize(double newSize, long seconds); + + /** + * Gets the current border center. + * + * @return The current border center. + */ + public Location getCenter(); + + /** + * Sets the new border center. + * + * @param x The new center x-coordinate. + * @param z The new center z-coordinate. + */ + public void setCenter(double x, double z); + + /** + * Sets the new border center. + * + * @param location The new location of the border center. (Only x/z used) + */ + public void setCenter(Location location); + + /** + * Gets the current border damage buffer. + * + * @return The current border damage buffer. + */ + public double getDamageBuffer(); + + /** + * Sets the amount of blocks a player may safely be outside the border before taking damage. + * + * @param blocks The amount of blocks. (The default is 5 blocks.) + */ + public void setDamageBuffer(double blocks); + + /** + * Gets the current border damage amount. + * + * @return The current border damage amount. + */ + public double getDamageAmount(); + + /** + * Sets the amount of damage a player takes when outside the border plus the border buffer. + * + * @param damage The amount of damage. (The default is 0.2 damage per second per block.) + */ + public void setDamageAmount(double damage); + + /** + * Gets the current border warning time in seconds. + * + * @return The current border warning time in seconds. + */ + public int getWarningTime(); + + /** + * Sets the warning time that causes the screen to be tinted red when a contracting border will reach the player within the specified time. + * + * @param seconds The amount of time in seconds. (The default is 15 seconds.) + */ + public void setWarningTime(int seconds); + + /** + * Gets the current border warning distance. + * + * @return The current border warning distance. + */ + public int getWarningDistance(); + + /** + * Sets the warning distance that causes the screen to be tinted red when the player is within the specified number of blocks from the border. + * + * @param distance The distance in blocks. (The default is 5 blocks.) + */ + public void setWarningDistance(int distance); + + /** + * Check if the specified location is inside this border. + * + * @param location the location to check + * @return if this location is inside the border or not + */ + public boolean isInside(Location location); +} diff --git a/src/main/java/org/bukkit/WorldCreator.java b/src/main/java/org/bukkit/WorldCreator.java new file mode 100644 index 00000000..53980fdb --- /dev/null +++ b/src/main/java/org/bukkit/WorldCreator.java @@ -0,0 +1,317 @@ +package org.bukkit; + +import java.util.Random; +import org.bukkit.command.CommandSender; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.Plugin; + +/** + * Represents various types of options that may be used to create a world. + */ +public class WorldCreator { + private final String name; + private long seed; + private World.Environment environment = World.Environment.NORMAL; + private ChunkGenerator generator = null; + private WorldType type = WorldType.NORMAL; + private boolean generateStructures = true; + private String generatorSettings = ""; + + /** + * Creates an empty WorldCreationOptions for the given world name + * + * @param name Name of the world that will be created + */ + public WorldCreator(String name) { + if (name == null) { + throw new IllegalArgumentException("World name cannot be null"); + } + + this.name = name; + this.seed = (new Random()).nextLong(); + } + + /** + * Copies the options from the specified world + * + * @param world World to copy options from + * @return This object, for chaining + */ + public WorldCreator copy(World world) { + if (world == null) { + throw new IllegalArgumentException("World cannot be null"); + } + + seed = world.getSeed(); + environment = world.getEnvironment(); + generator = world.getGenerator(); + + return this; + } + + /** + * Copies the options from the specified {@link WorldCreator} + * + * @param creator World creator to copy options from + * @return This object, for chaining + */ + public WorldCreator copy(WorldCreator creator) { + if (creator == null) { + throw new IllegalArgumentException("Creator cannot be null"); + } + + seed = creator.seed(); + environment = creator.environment(); + generator = creator.generator(); + + return this; + } + + /** + * Gets the name of the world that is to be loaded or created. + * + * @return World name + */ + public String name() { + return name; + } + + /** + * Gets the seed that will be used to create this world + * + * @return World seed + */ + public long seed() { + return seed; + } + + /** + * Sets the seed that will be used to create this world + * + * @param seed World seed + * @return This object, for chaining + */ + public WorldCreator seed(long seed) { + this.seed = seed; + + return this; + } + + /** + * Gets the environment that will be used to create or load the world + * + * @return World environment + */ + public World.Environment environment() { + return environment; + } + + /** + * Sets the environment that will be used to create or load the world + * + * @param env World environment + * @return This object, for chaining + */ + public WorldCreator environment(World.Environment env) { + this.environment = env; + + return this; + } + + /** + * Gets the type of the world that will be created or loaded + * + * @return World type + */ + public WorldType type() { + return type; + } + + /** + * Sets the type of the world that will be created or loaded + * + * @param type World type + * @return This object, for chaining + */ + public WorldCreator type(WorldType type) { + this.type = type; + + return this; + } + + /** + * Gets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + * + * @return Chunk generator + */ + public ChunkGenerator generator() { + return generator; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + * + * @param generator Chunk generator + * @return This object, for chaining + */ + public WorldCreator generator(ChunkGenerator generator) { + this.generator = generator; + + return this; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + *

+ * If the generator cannot be found for the given name, the natural + * environment generator will be used instead and a warning will be + * printed to the console. + * + * @param generator Name of the generator to use, in "plugin:id" notation + * @return This object, for chaining + */ + public WorldCreator generator(String generator) { + this.generator = getGeneratorForName(name, generator, Bukkit.getConsoleSender()); + + return this; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + *

+ * If the generator cannot be found for the given name, the natural + * environment generator will be used instead and a warning will be + * printed to the specified output + * + * @param generator Name of the generator to use, in "plugin:id" notation + * @param output {@link CommandSender} that will receive any error + * messages + * @return This object, for chaining + */ + public WorldCreator generator(String generator, CommandSender output) { + this.generator = getGeneratorForName(name, generator, output); + + return this; + } + + /** + * Sets the generator settings of the world that will be created or loaded + * + * @param generatorSettings The settings that should be used by the generator + * @return This object, for chaining + */ + public WorldCreator generatorSettings(String generatorSettings) { + this.generatorSettings = generatorSettings; + + return this; + } + + /** + * Gets the generator settings of the world that will be created or loaded + * + * @return The settings that should be used by the generator + */ + public String generatorSettings() { + return generatorSettings; + } + + /** + * Sets whether or not worlds created or loaded with this creator will + * have structures. + * + * @param generate Whether to generate structures + * @return This object, for chaining + */ + public WorldCreator generateStructures(boolean generate) { + this.generateStructures = generate; + + return this; + } + + /** + * Gets whether or not structures will be generated in the world. + * + * @return True if structures will be generated + */ + public boolean generateStructures() { + return generateStructures; + } + + /** + * Creates a world with the specified options. + *

+ * If the world already exists, it will be loaded from disk and some + * options may be ignored. + * + * @return Newly created or loaded world + */ + public World createWorld() { + return Bukkit.createWorld(this); + } + + /** + * Creates a new {@link WorldCreator} for the given world name + * + * @param name Name of the world to load or create + * @return Resulting WorldCreator + */ + public static WorldCreator name(String name) { + return new WorldCreator(name); + } + + /** + * Attempts to get the {@link ChunkGenerator} with the given name. + *

+ * If the generator is not found, null will be returned and a message will + * be printed to the specified {@link CommandSender} explaining why. + *

+ * The name must be in the "plugin:id" notation, or optionally just + * "plugin", where "plugin" is the safe-name of a plugin and "id" is an + * optional unique identifier for the generator you wish to request from + * the plugin. + * + * @param world Name of the world this will be used for + * @param name Name of the generator to retrieve + * @param output Where to output if errors are present + * @return Resulting generator, or null + */ + public static ChunkGenerator getGeneratorForName(String world, String name, CommandSender output) { + ChunkGenerator result = null; + + if (world == null) { + throw new IllegalArgumentException("World name must be specified"); + } + + if (output == null) { + output = Bukkit.getConsoleSender(); + } + + if (name != null) { + String[] split = name.split(":", 2); + String id = (split.length > 1) ? split[1] : null; + Plugin plugin = Bukkit.getPluginManager().getPlugin(split[0]); + + if (plugin == null) { + output.sendMessage("Could not set generator for world '" + world + "': Plugin '" + split[0] + "' does not exist"); + } else if (!plugin.isEnabled()) { + output.sendMessage("Could not set generator for world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' is not enabled"); + } else { + result = plugin.getDefaultWorldGenerator(world, id); + } + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/WorldType.java b/src/main/java/org/bukkit/WorldType.java new file mode 100644 index 00000000..6c810799 --- /dev/null +++ b/src/main/java/org/bukkit/WorldType.java @@ -0,0 +1,48 @@ +package org.bukkit; + +import com.google.common.collect.Maps; +import java.util.Map; + +/** + * Represents various types of worlds that may exist + */ +public enum WorldType { + NORMAL("DEFAULT"), + FLAT("FLAT"), + VERSION_1_1("DEFAULT_1_1"), + LARGE_BIOMES("LARGEBIOMES"), + AMPLIFIED("AMPLIFIED"), + CUSTOMIZED("CUSTOMIZED"); + + private final static Map BY_NAME = Maps.newHashMap(); + private final String name; + + private WorldType(String name) { + this.name = name; + } + + /** + * Gets the name of this WorldType + * + * @return Name of this type + */ + public String getName() { + return name; + } + + /** + * Gets a Worldtype by its name + * + * @param name Name of the WorldType to get + * @return Requested WorldType, or null if not found + */ + public static WorldType getByName(String name) { + return BY_NAME.get(name.toUpperCase(java.util.Locale.ENGLISH)); + } + + static { + for (WorldType type : values()) { + BY_NAME.put(type.name, type); + } + } +} diff --git a/src/main/java/org/bukkit/advancement/Advancement.java b/src/main/java/org/bukkit/advancement/Advancement.java new file mode 100644 index 00000000..c2bf3d5b --- /dev/null +++ b/src/main/java/org/bukkit/advancement/Advancement.java @@ -0,0 +1,18 @@ +package org.bukkit.advancement; + +import java.util.Collection; +import org.bukkit.Keyed; + +/** + * Represents an advancement that may be awarded to a player. This class is not + * reference safe as the underlying advancement may be reloaded. + */ +public interface Advancement extends Keyed { + + /** + * Get all the criteria present in this advancement. + * + * @return a unmodifiable copy of all criteria + */ + Collection getCriteria(); +} diff --git a/src/main/java/org/bukkit/advancement/AdvancementProgress.java b/src/main/java/org/bukkit/advancement/AdvancementProgress.java new file mode 100644 index 00000000..46b6637d --- /dev/null +++ b/src/main/java/org/bukkit/advancement/AdvancementProgress.java @@ -0,0 +1,64 @@ +package org.bukkit.advancement; + +import java.util.Collection; +import java.util.Date; + +/** + * The individual status of an advancement for a player. This class is not + * reference safe as the underlying advancement may be reloaded. + */ +public interface AdvancementProgress { + + /** + * The advancement this progress is concerning. + * + * @return the relevant advancement + */ + Advancement getAdvancement(); + + /** + * Check if all criteria for this advancement have been met. + * + * @return true if this advancement is done + */ + boolean isDone(); + + /** + * Mark the specified criteria as awarded at the current time. + * + * @param criteria the criteria to mark + * @return true if awarded, false if criteria does not exist or already + * awarded. + */ + boolean awardCriteria(String criteria); + + /** + * Mark the specified criteria as uncompleted. + * + * @param criteria the criteria to mark + * @return true if removed, false if criteria does not exist or not awarded + */ + boolean revokeCriteria(String criteria); + + /** + * Get the date the specified criteria was awarded. + * + * @param criteria the criteria to check + * @return date awarded or null if unawarded or criteria does not exist + */ + Date getDateAwarded(String criteria); + + /** + * Get the criteria which have not been awarded. + * + * @return unmodifiable copy of criteria remaining + */ + Collection getRemainingCriteria(); + + /** + * Gets the criteria which have been awarded. + * + * @return unmodifiable copy of criteria awarded + */ + Collection getAwardedCriteria(); +} diff --git a/src/main/java/org/bukkit/attribute/Attributable.java b/src/main/java/org/bukkit/attribute/Attributable.java new file mode 100644 index 00000000..155f13f1 --- /dev/null +++ b/src/main/java/org/bukkit/attribute/Attributable.java @@ -0,0 +1,16 @@ +package org.bukkit.attribute; + +/** + * Represents an object which may contain attributes. + */ +public interface Attributable { + + /** + * Gets the specified attribute instance from the object. This instance will + * be backed directly to the object and any changes will be visible at once. + * + * @param attribute the attribute to get + * @return the attribute instance or null if not applicable to this object + */ + AttributeInstance getAttribute(Attribute attribute); +} diff --git a/src/main/java/org/bukkit/attribute/Attribute.java b/src/main/java/org/bukkit/attribute/Attribute.java new file mode 100644 index 00000000..b282dc86 --- /dev/null +++ b/src/main/java/org/bukkit/attribute/Attribute.java @@ -0,0 +1,56 @@ +package org.bukkit.attribute; + +/** + * Types of attributes which may be present on an {@link Attributable}. + */ +public enum Attribute { + + /** + * Maximum health of an Entity. + */ + GENERIC_MAX_HEALTH, + /** + * Range at which an Entity will follow others. + */ + GENERIC_FOLLOW_RANGE, + /** + * Resistance of an Entity to knockback. + */ + GENERIC_KNOCKBACK_RESISTANCE, + /** + * Movement speed of an Entity. + */ + GENERIC_MOVEMENT_SPEED, + /** + * Flying speed of an Entity. + */ + GENERIC_FLYING_SPEED, + /** + * Attack damage of an Entity. + */ + GENERIC_ATTACK_DAMAGE, + /** + * Attack speed of an Entity. + */ + GENERIC_ATTACK_SPEED, + /** + * Armor bonus of an Entity. + */ + GENERIC_ARMOR, + /** + * Armor durability bonus of an Entity. + */ + GENERIC_ARMOR_TOUGHNESS, + /** + * Luck bonus of an Entity. + */ + GENERIC_LUCK, + /** + * Strength with which a horse will jump. + */ + HORSE_JUMP_STRENGTH, + /** + * Chance of a zombie to spawn reinforcements. + */ + ZOMBIE_SPAWN_REINFORCEMENTS; +} diff --git a/src/main/java/org/bukkit/attribute/AttributeInstance.java b/src/main/java/org/bukkit/attribute/AttributeInstance.java new file mode 100644 index 00000000..ca8b76a6 --- /dev/null +++ b/src/main/java/org/bukkit/attribute/AttributeInstance.java @@ -0,0 +1,67 @@ +package org.bukkit.attribute; + +import java.util.Collection; + +/** + * Represents a mutable instance of an attribute and its associated modifiers + * and values. + */ +public interface AttributeInstance { + + /** + * The attribute pertaining to this instance. + * + * @return the attribute + */ + Attribute getAttribute(); + + /** + * Base value of this instance before modifiers are applied. + * + * @return base value + */ + double getBaseValue(); + + /** + * Set the base value of this instance. + * + * @param value new base value + */ + void setBaseValue(double value); + + /** + * Get all modifiers present on this instance. + * + * @return a copied collection of all modifiers + */ + Collection getModifiers(); + + /** + * Add a modifier to this instance. + * + * @param modifier to add + */ + void addModifier(AttributeModifier modifier); + + /** + * Remove a modifier from this instance. + * + * @param modifier to remove + */ + void removeModifier(AttributeModifier modifier); + + /** + * Get the value of this instance after all associated modifiers have been + * applied. + * + * @return the total attribute value + */ + double getValue(); + + /** + * Gets the default value of the Attribute attached to this instance. + * + * @return server default value + */ + double getDefaultValue(); +} diff --git a/src/main/java/org/bukkit/attribute/AttributeModifier.java b/src/main/java/org/bukkit/attribute/AttributeModifier.java new file mode 100644 index 00000000..c13f38e0 --- /dev/null +++ b/src/main/java/org/bukkit/attribute/AttributeModifier.java @@ -0,0 +1,103 @@ +package org.bukkit.attribute; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.apache.commons.lang3.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.NumberConversions; + +/** + * Concrete implementation of an attribute modifier. + */ +public class AttributeModifier implements ConfigurationSerializable { + + private final UUID uuid; + private final String name; + private final double amount; + private final Operation operation; + + public AttributeModifier(String name, double amount, Operation operation) { + this(UUID.randomUUID(), name, amount, operation); + } + + public AttributeModifier(UUID uuid, String name, double amount, Operation operation) { + Validate.notNull(uuid, "uuid"); + Validate.notEmpty(name, "Name cannot be empty"); + Validate.notNull(operation, "operation"); + + this.uuid = uuid; + this.name = name; + this.amount = amount; + this.operation = operation; + } + + /** + * Get the unique ID for this modifier. + * + * @return unique id + */ + public UUID getUniqueId() { + return uuid; + } + + /** + * Get the name of this modifier. + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Get the amount by which this modifier will apply its {@link Operation}. + * + * @return modification amount + */ + public double getAmount() { + return amount; + } + + /** + * Get the operation this modifier will apply. + * + * @return operation + */ + public Operation getOperation() { + return operation; + } + + @Override + public Map serialize() { + Map data = new HashMap(); + data.put("uuid", uuid.toString()); + data.put("name", name); + data.put("operation", operation.ordinal()); + data.put("amount", amount); + return data; + } + + public static AttributeModifier deserialize(Map args) { + return new AttributeModifier(UUID.fromString((String) args.get("uuid")), (String) args.get("name"), NumberConversions.toDouble(args.get("amount")), Operation.values()[NumberConversions.toInt(args.get("operation"))]); + } + + /** + * Enumerable operation to be applied. + */ + public enum Operation { + + /** + * Adds (or subtracts) the specified amount to the base value. + */ + ADD_NUMBER, + /** + * Adds this scalar of amount to the base value. + */ + ADD_SCALAR, + /** + * Multiply amount by this value, after adding 1 to it. + */ + MULTIPLY_SCALAR_1; + } +} diff --git a/src/main/java/org/bukkit/block/Banner.java b/src/main/java/org/bukkit/block/Banner.java new file mode 100644 index 00000000..cabf4744 --- /dev/null +++ b/src/main/java/org/bukkit/block/Banner.java @@ -0,0 +1,80 @@ +package org.bukkit.block; + +import org.bukkit.DyeColor; +import org.bukkit.block.banner.Pattern; + +import java.util.List; + +/** + * Represents a captured state of a banner. + */ +public interface Banner extends BlockState { + + /** + * Returns the base color for this banner + * + * @return the base color + */ + DyeColor getBaseColor(); + + /** + * Sets the base color for this banner + * + * @param color the base color + */ + void setBaseColor(DyeColor color); + + /** + * Returns a list of patterns on this banner + * + * @return the patterns + */ + List getPatterns(); + + /** + * Sets the patterns used on this banner + * + * @param patterns the new list of patterns + */ + void setPatterns(List patterns); + + /** + * Adds a new pattern on top of the existing + * patterns + * + * @param pattern the new pattern to add + */ + void addPattern(Pattern pattern); + + /** + * Returns the pattern at the specified index + * + * @param i the index + * @return the pattern + */ + Pattern getPattern(int i); + + /** + * Removes the pattern at the specified index + * + * @param i the index + * @return the removed pattern + */ + Pattern removePattern(int i); + + /** + * Sets the pattern at the specified index + * + * @param i the index + * @param pattern the new pattern + */ + void setPattern(int i, Pattern pattern); + + /** + * Returns the number of patterns on this + * banner + * + * @return the number of patterns + */ + int numberOfPatterns(); +} diff --git a/src/main/java/org/bukkit/block/Beacon.java b/src/main/java/org/bukkit/block/Beacon.java new file mode 100644 index 00000000..97d39213 --- /dev/null +++ b/src/main/java/org/bukkit/block/Beacon.java @@ -0,0 +1,69 @@ +package org.bukkit.block; + +import java.util.Collection; +import org.bukkit.Nameable; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.BeaconInventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +/** + * Represents a captured state of a beacon. + */ +public interface Beacon extends Container, Nameable { + + @Override + BeaconInventory getInventory(); + + @Override + BeaconInventory getSnapshotInventory(); + + /** + * Returns the list of players within the beacon's range of effect. + *

+ * This will return an empty list if the block represented by this state is + * no longer a beacon. + * + * @return the players in range + * @throws IllegalStateException if this block state is not placed + */ + Collection getEntitiesInRange(); + + /** + * Returns the tier of the beacon pyramid (0-4). The tier refers to the + * beacon's power level, based on how many layers of blocks are in the + * pyramid. Tier 1 refers to a beacon with one layer of 9 blocks under it. + * + * @return the beacon tier + */ + int getTier(); + + /** + * Returns the primary effect set on the beacon + * + * @return the primary effect or null if not set + */ + PotionEffect getPrimaryEffect(); + + /** + * Set the primary effect on this beacon, or null to clear. + * + * @param effect new primary effect + */ + void setPrimaryEffect(PotionEffectType effect); + + /** + * Returns the secondary effect set on the beacon. + * + * @return the secondary effect or null if no secondary effect + */ + PotionEffect getSecondaryEffect(); + + /** + * Set the secondary effect on this beacon, or null to clear. Note that tier + * must be >= 4 for this effect to be active. + * + * @param effect desired secondary effect + */ + void setSecondaryEffect(PotionEffectType effect); +} diff --git a/src/main/java/org/bukkit/block/Bed.java b/src/main/java/org/bukkit/block/Bed.java new file mode 100644 index 00000000..17a807a9 --- /dev/null +++ b/src/main/java/org/bukkit/block/Bed.java @@ -0,0 +1,8 @@ +package org.bukkit.block; + +import org.bukkit.material.Colorable; + +/** + * Represents a captured state of a bed. + */ +public interface Bed extends BlockState, Colorable { } diff --git a/src/main/java/org/bukkit/block/Biome.java b/src/main/java/org/bukkit/block/Biome.java new file mode 100644 index 00000000..9585005f --- /dev/null +++ b/src/main/java/org/bukkit/block/Biome.java @@ -0,0 +1,69 @@ +package org.bukkit.block; + +/** + * Holds all accepted Biomes in the default server + */ +public enum Biome { + OCEAN, + PLAINS, + DESERT, + EXTREME_HILLS, + FOREST, + TAIGA, + SWAMPLAND, + RIVER, + HELL, + SKY, + FROZEN_OCEAN, + FROZEN_RIVER, + ICE_FLATS, + ICE_MOUNTAINS, + MUSHROOM_ISLAND, + MUSHROOM_ISLAND_SHORE, + BEACHES, + DESERT_HILLS, + FOREST_HILLS, + TAIGA_HILLS, + SMALLER_EXTREME_HILLS, + JUNGLE, + JUNGLE_HILLS, + JUNGLE_EDGE, + DEEP_OCEAN, + STONE_BEACH, + COLD_BEACH, + BIRCH_FOREST, + BIRCH_FOREST_HILLS, + ROOFED_FOREST, + TAIGA_COLD, + TAIGA_COLD_HILLS, + REDWOOD_TAIGA, + REDWOOD_TAIGA_HILLS, + EXTREME_HILLS_WITH_TREES, + SAVANNA, + SAVANNA_ROCK, + MESA, + MESA_ROCK, + MESA_CLEAR_ROCK, + VOID, + MUTATED_PLAINS, + MUTATED_DESERT, + MUTATED_EXTREME_HILLS, + MUTATED_FOREST, + MUTATED_TAIGA, + MUTATED_SWAMPLAND, + MUTATED_ICE_FLATS, + MUTATED_JUNGLE, + MUTATED_JUNGLE_EDGE, + MUTATED_BIRCH_FOREST, + MUTATED_BIRCH_FOREST_HILLS, + MUTATED_ROOFED_FOREST, + MUTATED_TAIGA_COLD, + MUTATED_REDWOOD_TAIGA, + MUTATED_REDWOOD_TAIGA_HILLS, + MUTATED_EXTREME_HILLS_WITH_TREES, + MUTATED_SAVANNA, + MUTATED_SAVANNA_ROCK, + MUTATED_MESA, + MUTATED_MESA_ROCK, + MUTATED_MESA_CLEAR_ROCK +} diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java new file mode 100644 index 00000000..235c15bd --- /dev/null +++ b/src/main/java/org/bukkit/block/Block.java @@ -0,0 +1,393 @@ +package org.bukkit.block; + +import java.util.Collection; + +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.Metadatable; + +/** + * Represents a block. This is a live object, and only one Block may exist for + * any given location in a world. The state of the block may change + * concurrently to your own handling of it; use block.getState() to get a + * snapshot state of a block which will not be modified. + */ +public interface Block extends Metadatable { + + /** + * Gets the metadata for this block + * + * @return block specific metadata + * @deprecated Magic value + */ + @Deprecated + byte getData(); + + /** + * Gets the block at the given offsets + * + * @param modX X-coordinate offset + * @param modY Y-coordinate offset + * @param modZ Z-coordinate offset + * @return Block at the given offsets + */ + Block getRelative(int modX, int modY, int modZ); + + /** + * Gets the block at the given face + *

+ * This method is equal to getRelative(face, 1) + * + * @param face Face of this block to return + * @return Block at the given face + * @see #getRelative(BlockFace, int) + */ + Block getRelative(BlockFace face); + + /** + * Gets the block at the given distance of the given face + *

+ * For example, the following method places water at 100,102,100; two + * blocks above 100,100,100. + * + *

+     * Block block = world.getBlockAt(100, 100, 100);
+     * Block shower = block.getRelative(BlockFace.UP, 2);
+     * shower.setType(Material.WATER);
+     * 
+ * + * @param face Face of this block to return + * @param distance Distance to get the block at + * @return Block at the given face + */ + Block getRelative(BlockFace face, int distance); + + /** + * Gets the type of this block + * + * @return block type + */ + Material getType(); + + /** + * Gets the type-id of this block + * + * @return block type-id + * @deprecated Magic value + */ + @Deprecated + int getTypeId(); + + /** + * Gets the light level between 0-15 + * + * @return light level + */ + byte getLightLevel(); + + /** + * Get the amount of light at this block from the sky. + *

+ * Any light given from other sources (such as blocks like torches) will + * be ignored. + * + * @return Sky light level + */ + byte getLightFromSky(); + + /** + * Get the amount of light at this block from nearby blocks. + *

+ * Any light given from other sources (such as the sun) will be ignored. + * + * @return Block light level + */ + byte getLightFromBlocks(); + + /** + * Gets the world which contains this Block + * + * @return World containing this block + */ + World getWorld(); + + /** + * Gets the x-coordinate of this block + * + * @return x-coordinate + */ + int getX(); + + /** + * Gets the y-coordinate of this block + * + * @return y-coordinate + */ + int getY(); + + /** + * Gets the z-coordinate of this block + * + * @return z-coordinate + */ + int getZ(); + + /** + * Gets the Location of the block + * + * @return Location of block + */ + Location getLocation(); + + /** + * Stores the location of the block in the provided Location object. + *

+ * If the provided Location is null this method does nothing and returns + * null. + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + Location getLocation(Location loc); + + /** + * Gets the chunk which contains this block + * + * @return Containing Chunk + */ + Chunk getChunk(); + + /** + * Sets the metadata for this block + * + * @param data New block specific metadata + * @deprecated Magic value + */ + @Deprecated + void setData(byte data); + + /** + * Sets the metadata for this block + * + * @param data New block specific metadata + * @param applyPhysics False to cancel physics from the changed block. + * @deprecated Magic value + */ + @Deprecated + void setData(byte data, boolean applyPhysics); + + /** + * Sets the type of this block + * + * @param type Material to change this block to + */ + void setType(Material type); + + /** + * Sets the type of this block + * + * @param type Material to change this block to + * @param applyPhysics False to cancel physics on the changed block. + */ + void setType(Material type, boolean applyPhysics); + + /** + * Sets the type-id of this block + * + * @param type Type-Id to change this block to + * @return whether the block was changed + * @deprecated Magic value + */ + @Deprecated + boolean setTypeId(int type); + + /** + * Sets the type-id of this block + * + * @param type Type-Id to change this block to + * @param applyPhysics False to cancel physics on the changed block. + * @return whether the block was changed + * @deprecated Magic value + */ + @Deprecated + boolean setTypeId(int type, boolean applyPhysics); + + /** + * Sets the type-id of this block + * + * @param type Type-Id to change this block to + * @param data The data value to change this block to + * @param applyPhysics False to cancel physics on the changed block + * @return whether the block was changed + * @deprecated Magic value + */ + @Deprecated + boolean setTypeIdAndData(int type, byte data, boolean applyPhysics); + + /** + * Gets the face relation of this block compared to the given block. + *

+ * For example: + *

{@code
+     * Block current = world.getBlockAt(100, 100, 100);
+     * Block target = world.getBlockAt(100, 101, 100);
+     *
+     * current.getFace(target) == BlockFace.Up;
+     * }
+ *
+ * If the given block is not connected to this block, null may be returned + * + * @param block Block to compare against this block + * @return BlockFace of this block which has the requested block, or null + */ + BlockFace getFace(Block block); + + /** + * Captures the current state of this block. You may then cast that state + * into any accepted type, such as Furnace or Sign. + *

+ * The returned object will never be updated, and you are not guaranteed + * that (for example) a sign is still a sign after you capture its state. + * + * @return BlockState with the current state of this block. + */ + BlockState getState(); + + /** + * Returns the biome that this block resides in + * + * @return Biome type containing this block + */ + Biome getBiome(); + + /** + * Sets the biome that this block resides in + * + * @param bio new Biome type for this block + */ + void setBiome(Biome bio); + + /** + * Returns true if the block is being powered by Redstone. + * + * @return True if the block is powered. + */ + boolean isBlockPowered(); + + /** + * Returns true if the block is being indirectly powered by Redstone. + * + * @return True if the block is indirectly powered. + */ + boolean isBlockIndirectlyPowered(); + + /** + * Returns true if the block face is being powered by Redstone. + * + * @param face The block face + * @return True if the block face is powered. + */ + boolean isBlockFacePowered(BlockFace face); + + /** + * Returns true if the block face is being indirectly powered by Redstone. + * + * @param face The block face + * @return True if the block face is indirectly powered. + */ + boolean isBlockFaceIndirectlyPowered(BlockFace face); + + /** + * Returns the redstone power being provided to this block face + * + * @param face the face of the block to query or BlockFace.SELF for the + * block itself + * @return The power level. + */ + int getBlockPower(BlockFace face); + + /** + * Returns the redstone power being provided to this block + * + * @return The power level. + */ + int getBlockPower(); + + /** + * Checks if this block is empty. + *

+ * A block is considered empty when {@link #getType()} returns {@link + * Material#AIR}. + * + * @return true if this block is empty + */ + boolean isEmpty(); + + /** + * Checks if this block is liquid. + *

+ * A block is considered liquid when {@link #getType()} returns {@link + * Material#WATER}, {@link Material#STATIONARY_WATER}, {@link + * Material#LAVA} or {@link Material#STATIONARY_LAVA}. + * + * @return true if this block is liquid + */ + boolean isLiquid(); + + /** + * Gets the temperature of the biome of this block + * + * @return Temperature of this block + */ + double getTemperature(); + + /** + * Gets the humidity of the biome of this block + * + * @return Humidity of this block + */ + double getHumidity(); + + /** + * Returns the reaction of the block when moved by a piston + * + * @return reaction + */ + PistonMoveReaction getPistonMoveReaction(); + + /** + * Breaks the block and spawns items as if a player had digged it + * + * @return true if the block was destroyed + */ + boolean breakNaturally(); + + /** + * Breaks the block and spawns items as if a player had digged it with a + * specific tool + * + * @param tool The tool or item in hand used for digging + * @return true if the block was destroyed + */ + boolean breakNaturally(ItemStack tool); + + /** + * Returns a list of items which would drop by destroying this block + * + * @return a list of dropped items for this type of block + */ + Collection getDrops(); + + /** + * Returns a list of items which would drop by destroying this block with + * a specific tool + * + * @param tool The tool or item in hand used for digging + * @return a list of dropped items for this type of block + */ + Collection getDrops(ItemStack tool); + +} diff --git a/src/main/java/org/bukkit/block/BlockFace.java b/src/main/java/org/bukkit/block/BlockFace.java new file mode 100644 index 00000000..58fb195d --- /dev/null +++ b/src/main/java/org/bukkit/block/BlockFace.java @@ -0,0 +1,132 @@ +package org.bukkit.block; + +/** + * Represents the face of a block + */ +public enum BlockFace { + NORTH(0, 0, -1), + EAST(1, 0, 0), + SOUTH(0, 0, 1), + WEST(-1, 0, 0), + UP(0, 1, 0), + DOWN(0, -1, 0), + NORTH_EAST(NORTH, EAST), + NORTH_WEST(NORTH, WEST), + SOUTH_EAST(SOUTH, EAST), + SOUTH_WEST(SOUTH, WEST), + WEST_NORTH_WEST(WEST, NORTH_WEST), + NORTH_NORTH_WEST(NORTH, NORTH_WEST), + NORTH_NORTH_EAST(NORTH, NORTH_EAST), + EAST_NORTH_EAST(EAST, NORTH_EAST), + EAST_SOUTH_EAST(EAST, SOUTH_EAST), + SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST), + SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST), + WEST_SOUTH_WEST(WEST, SOUTH_WEST), + SELF(0, 0, 0); + + private final int modX; + private final int modY; + private final int modZ; + + private BlockFace(final int modX, final int modY, final int modZ) { + this.modX = modX; + this.modY = modY; + this.modZ = modZ; + } + + private BlockFace(final BlockFace face1, final BlockFace face2) { + this.modX = face1.getModX() + face2.getModX(); + this.modY = face1.getModY() + face2.getModY(); + this.modZ = face1.getModZ() + face2.getModZ(); + } + + /** + * Get the amount of X-coordinates to modify to get the represented block + * + * @return Amount of X-coordinates to modify + */ + public int getModX() { + return modX; + } + + /** + * Get the amount of Y-coordinates to modify to get the represented block + * + * @return Amount of Y-coordinates to modify + */ + public int getModY() { + return modY; + } + + /** + * Get the amount of Z-coordinates to modify to get the represented block + * + * @return Amount of Z-coordinates to modify + */ + public int getModZ() { + return modZ; + } + + public BlockFace getOppositeFace() { + switch (this) { + case NORTH: + return BlockFace.SOUTH; + + case SOUTH: + return BlockFace.NORTH; + + case EAST: + return BlockFace.WEST; + + case WEST: + return BlockFace.EAST; + + case UP: + return BlockFace.DOWN; + + case DOWN: + return BlockFace.UP; + + case NORTH_EAST: + return BlockFace.SOUTH_WEST; + + case NORTH_WEST: + return BlockFace.SOUTH_EAST; + + case SOUTH_EAST: + return BlockFace.NORTH_WEST; + + case SOUTH_WEST: + return BlockFace.NORTH_EAST; + + case WEST_NORTH_WEST: + return BlockFace.EAST_SOUTH_EAST; + + case NORTH_NORTH_WEST: + return BlockFace.SOUTH_SOUTH_EAST; + + case NORTH_NORTH_EAST: + return BlockFace.SOUTH_SOUTH_WEST; + + case EAST_NORTH_EAST: + return BlockFace.WEST_SOUTH_WEST; + + case EAST_SOUTH_EAST: + return BlockFace.WEST_NORTH_WEST; + + case SOUTH_SOUTH_EAST: + return BlockFace.NORTH_NORTH_WEST; + + case SOUTH_SOUTH_WEST: + return BlockFace.NORTH_NORTH_EAST; + + case WEST_SOUTH_WEST: + return BlockFace.EAST_NORTH_EAST; + + case SELF: + return BlockFace.SELF; + } + + return BlockFace.SELF; + } +} diff --git a/src/main/java/org/bukkit/block/BlockState.java b/src/main/java/org/bukkit/block/BlockState.java new file mode 100644 index 00000000..4b13d494 --- /dev/null +++ b/src/main/java/org/bukkit/block/BlockState.java @@ -0,0 +1,216 @@ +package org.bukkit.block; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.Metadatable; + +/** + * Represents a captured state of a block, which will not change + * automatically. + *

+ * Unlike Block, which only one object can exist per coordinate, BlockState + * can exist multiple times for any given Block. Note that another plugin may + * change the state of the block and you will not know, or they may change the + * block to another type entirely, causing your BlockState to become invalid. + */ +public interface BlockState extends Metadatable { + + /** + * Gets the block represented by this block state. + * + * @return the block represented by this block state + * @throws IllegalStateException if this block state is not placed + */ + Block getBlock(); + + /** + * Gets the metadata for this block state. + * + * @return block specific metadata + */ + MaterialData getData(); + + /** + * Gets the type of this block state. + * + * @return block type + */ + Material getType(); + + /** + * Gets the type-id of this block state. + * + * @return block type-id + * @deprecated Magic value + */ + @Deprecated + int getTypeId(); + + /** + * Gets the current light level of the block represented by this block state. + * + * @return the light level between 0-15 + * @throws IllegalStateException if this block state is not placed + */ + byte getLightLevel(); + + /** + * Gets the world which contains the block represented by this block state. + * + * @return the world containing the block represented by this block state + * @throws IllegalStateException if this block state is not placed + */ + World getWorld(); + + /** + * Gets the x-coordinate of this block state. + * + * @return x-coordinate + */ + int getX(); + + /** + * Gets the y-coordinate of this block state. + * + * @return y-coordinate + */ + int getY(); + + /** + * Gets the z-coordinate of this block state. + * + * @return z-coordinate + */ + int getZ(); + + /** + * Gets the location of this block state. + *

+ * If this block state is not placed the location's world will be null! + * + * @return the location + */ + Location getLocation(); + + /** + * Stores the location of this block state in the provided Location object. + *

+ * If the provided Location is null this method does nothing and returns + * null. + *

+ * If this block state is not placed the location's world will be null! + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + Location getLocation(Location loc); + + /** + * Gets the chunk which contains the block represented by this block state. + * + * @return the containing Chunk + * @throws IllegalStateException if this block state is not placed + */ + Chunk getChunk(); + + /** + * Sets the metadata for this block state. + * + * @param data New block specific metadata + */ + void setData(MaterialData data); + + /** + * Sets the type of this block state. + * + * @param type Material to change this block state to + */ + void setType(Material type); + + /** + * Sets the type-id of this block state. + * + * @param type Type-Id to change this block state to + * @return Whether it worked? + * @deprecated Magic value + */ + @Deprecated + boolean setTypeId(int type); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * This has the same effect as calling update(false). That is to say, + * this will not modify the state of a block if it is no longer the same + * type as it was when this state was taken. It will return false in this + * eventuality. + * + * @return true if the update was successful, otherwise false + * @see #update(boolean) + */ + boolean update(); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * This has the same effect as calling update(force, true). That is to + * say, this will trigger a physics update to surrounding blocks. + * + * @param force true to forcefully set the state + * @return true if the update was successful, otherwise false + */ + boolean update(boolean force); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * If this state is not placed, this will have no effect and return true. + *

+ * Unless force is true, this will not modify the state of a block if it + * is no longer the same type as it was when this state was taken. It will + * return false in this eventuality. + *

+ * If force is true, it will set the type of the block to match the new + * state, set the state data and then return true. + *

+ * If applyPhysics is true, it will trigger a physics update on + * surrounding blocks which could cause them to update or disappear. + * + * @param force true to forcefully set the state + * @param applyPhysics false to cancel updating physics on surrounding + * blocks + * @return true if the update was successful, otherwise false + */ + boolean update(boolean force, boolean applyPhysics); + + /** + * @return The data as a raw byte. + * @deprecated Magic value + */ + @Deprecated + public byte getRawData(); + + /** + * @param data The new data value for the block. + * @deprecated Magic value + */ + @Deprecated + public void setRawData(byte data); + + /** + * Returns whether this state is placed in the world. + *

+ * Some methods will not work if the block state isn't + * placed in the world. + * + * @return whether the state is placed in the world + * or 'virtual' (e.g. on an itemstack) + */ + boolean isPlaced(); +} diff --git a/src/main/java/org/bukkit/block/BrewingStand.java b/src/main/java/org/bukkit/block/BrewingStand.java new file mode 100644 index 00000000..f276c304 --- /dev/null +++ b/src/main/java/org/bukkit/block/BrewingStand.java @@ -0,0 +1,44 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.inventory.BrewerInventory; + +/** + * Represents a captured state of a brewing stand. + */ +public interface BrewingStand extends Container, Nameable { + + /** + * How much time is left in the brewing cycle. + * + * @return Brew Time + */ + int getBrewingTime(); + + /** + * Set the time left before brewing completes. + * + * @param brewTime Brewing time + */ + void setBrewingTime(int brewTime); + + /** + * Get the level of current fuel for brewing. + * + * @return The fuel level + */ + int getFuelLevel(); + + /** + * Set the level of current fuel for brewing. + * + * @param level fuel level + */ + void setFuelLevel(int level); + + @Override + BrewerInventory getInventory(); + + @Override + BrewerInventory getSnapshotInventory(); +} diff --git a/src/main/java/org/bukkit/block/Chest.java b/src/main/java/org/bukkit/block/Chest.java new file mode 100644 index 00000000..97dc7813 --- /dev/null +++ b/src/main/java/org/bukkit/block/Chest.java @@ -0,0 +1,26 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.inventory.Inventory; + +/** + * Represents a captured state of a chest. + */ +public interface Chest extends Container, Nameable { + + /** + * Gets the inventory of the chest block represented by this block state. + *

+ * If the chest is a double chest, it returns just the portion of the + * inventory linked to the half of the chest corresponding to this block state. + *

+ * If the block was changed to a different type in the meantime, the + * returned inventory might no longer be valid. + *

+ * If this block state is not placed this will return the captured + * inventory snapshot instead. + * + * @return the inventory + */ + Inventory getBlockInventory(); +} diff --git a/src/main/java/org/bukkit/block/CommandBlock.java b/src/main/java/org/bukkit/block/CommandBlock.java new file mode 100644 index 00000000..f94856cf --- /dev/null +++ b/src/main/java/org/bukkit/block/CommandBlock.java @@ -0,0 +1,43 @@ +package org.bukkit.block; + +/** + * Represents a captured state of a command block. + */ +public interface CommandBlock extends BlockState { + + /** + * Gets the command that this CommandBlock will run when powered. + * This will never return null. If the CommandBlock does not have a + * command, an empty String will be returned instead. + * + * @return Command that this CommandBlock will run when powered. + */ + public String getCommand(); + + /** + * Sets the command that this CommandBlock will run when powered. + * Setting the command to null is the same as setting it to an empty + * String. + * + * @param command Command that this CommandBlock will run when powered. + */ + public void setCommand(String command); + + /** + * Gets the name of this CommandBlock. The name is used with commands + * that this CommandBlock executes. This name will never be null, and + * by default is "@". + * + * @return Name of this CommandBlock. + */ + public String getName(); + + /** + * Sets the name of this CommandBlock. The name is used with commands + * that this CommandBlock executes. Setting the name to null is the + * same as setting it to "@". + * + * @param name New name for this CommandBlock. + */ + public void setName(String name); +} diff --git a/src/main/java/org/bukkit/block/Comparator.java b/src/main/java/org/bukkit/block/Comparator.java new file mode 100644 index 00000000..c9acc916 --- /dev/null +++ b/src/main/java/org/bukkit/block/Comparator.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of an on / off comparator. + */ +public interface Comparator extends BlockState { } diff --git a/src/main/java/org/bukkit/block/Container.java b/src/main/java/org/bukkit/block/Container.java new file mode 100644 index 00000000..9eee5cc0 --- /dev/null +++ b/src/main/java/org/bukkit/block/Container.java @@ -0,0 +1,36 @@ +package org.bukkit.block; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a captured state of a container block. + */ +public interface Container extends BlockState, InventoryHolder, Lockable { + + /** + * Gets the inventory of the block represented by this block state. + *

+ * If the block was changed to a different type in the meantime, the + * returned inventory might no longer be valid. + *

+ * If this block state is not placed this will return the captured inventory + * snapshot instead. + * + * @return the inventory + */ + @Override + Inventory getInventory(); + + /** + * Gets the captured inventory snapshot of this container. + *

+ * The returned inventory is not linked to any block. Any modifications to + * the returned inventory will not be applied to the block represented by + * this block state up until {@link #update(boolean, boolean)} has been + * called. + * + * @return the captured inventory snapshot + */ + Inventory getSnapshotInventory(); +} diff --git a/src/main/java/org/bukkit/block/CraftCustomContainer.java b/src/main/java/org/bukkit/block/CraftCustomContainer.java new file mode 100644 index 00000000..a6ae5ede --- /dev/null +++ b/src/main/java/org/bukkit/block/CraftCustomContainer.java @@ -0,0 +1,32 @@ +package org.bukkit.block; + +import net.minecraft.inventory.IInventory; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * CraftCustomContainer + * + * @author Hexeption admin@hexeption.co.uk - Cauldron + * @since 30/06/2019 - 01:56 PM + */ +public class CraftCustomContainer extends CraftBlockState implements InventoryHolder { + + private final CraftWorld world; + private final IInventory container; + + public CraftCustomContainer(Block block){ + super(block); + world = (CraftWorld) block.getWorld(); + container = (IInventory) world.getTileEntityAt(getX(), getY(), getZ()); + } + + @Override + public Inventory getInventory() { + CraftInventory inventory = new CraftInventory(container); + return inventory; + } +} diff --git a/src/main/java/org/bukkit/block/CreatureSpawner.java b/src/main/java/org/bukkit/block/CreatureSpawner.java new file mode 100644 index 00000000..c8887f36 --- /dev/null +++ b/src/main/java/org/bukkit/block/CreatureSpawner.java @@ -0,0 +1,199 @@ +package org.bukkit.block; + +import org.bukkit.entity.EntityType; + +/** + * Represents a captured state of a creature spawner. + */ +public interface CreatureSpawner extends BlockState { + + /** + * Get the spawner's creature type. + * + * @return The creature type. + */ + public EntityType getSpawnedType(); + + /** + * Set the spawner's creature type. + * + * @param creatureType The creature type. + */ + public void setSpawnedType(EntityType creatureType); + + /** + * Set the spawner mob type. + * + * @param creatureType The creature type's name. + * @deprecated magic value, use + * {@link #setSpawnedType(EntityType)}. + */ + @Deprecated + public void setCreatureTypeByName(String creatureType); + + /** + * Get the spawner's creature type. + * + * @return The creature type's name. + * @deprecated magic value, use {@link #getSpawnedType()}. + */ + @Deprecated + public String getCreatureTypeName(); + + /** + * Get the spawner's delay. + *
+ * This is the delay, in ticks, until the spawner will spawn its next mob. + * + * @return The delay. + */ + public int getDelay(); + + /** + * Set the spawner's delay. + *
+ * If set to -1, the spawn delay will be reset to a random value between + * {@link #getMinSpawnDelay} and {@link #getMaxSpawnDelay()}. + * + * @param delay The delay. + */ + public void setDelay(int delay); + + /** + * The minimum spawn delay amount (in ticks). + *
+ * This value is used when the spawner resets its delay (for any reason). + * It will choose a random number between {@link #getMinSpawnDelay()} + * and {@link #getMaxSpawnDelay()} for its next {@link #getDelay()}. + * + * Default value is 200 ticks. + * + * @return the minimum spawn delay amount + */ + public int getMinSpawnDelay(); + + /** + * Set the minimum spawn delay amount (in ticks). + * + * @see #getMinSpawnDelay() + * @param delay the minimum spawn delay amount + */ + public void setMinSpawnDelay(int delay); + + /** + * The maximum spawn delay amount (in ticks). + *
+ * This value is used when the spawner resets its delay (for any reason). + * It will choose a random number between {@link #getMinSpawnDelay()} + * and {@link #getMaxSpawnDelay()} for its next {@link #getDelay()}. + *
+ * This value must be greater than 0 and less than or equal to + * {@link #getMaxSpawnDelay()}. + * + * Default value is 800 ticks. + * + * @return the maximum spawn delay amount + */ + public int getMaxSpawnDelay(); + + /** + * Set the maximum spawn delay amount (in ticks). + *
+ * This value must be greater than 0, as well as greater than or + * equal to {@link #getMinSpawnDelay()} + * + * @see #getMaxSpawnDelay() + * @param delay the new maximum spawn delay amount + */ + public void setMaxSpawnDelay(int delay); + + /** + * Get how many mobs attempt to spawn. + *
+ * Default value is 4. + * + * @return the current spawn count + */ + public int getSpawnCount(); + + /** + * Set how many mobs attempt to spawn. + * + * @param spawnCount the new spawn count + */ + public void setSpawnCount(int spawnCount); + + /** + * Set the new maximum amount of similar entities that are allowed to be + * within spawning range of this spawner. + *
+ * If more than the maximum number of entities are within range, the spawner + * will not spawn and try again with a new {@link #getDelay()}. + *
+ * Default value is 16. + * + * @return the maximum number of nearby, similar, entities + */ + public int getMaxNearbyEntities(); + + /** + * Set the maximum number of similar entities that are allowed to be within + * spawning range of this spawner. + *
+ * Similar entities are entities that are of the same {@link EntityType} + * + * @param maxNearbyEntities the maximum number of nearby, similar, entities + */ + public void setMaxNearbyEntities(int maxNearbyEntities); + + /** + * Get the maximum distance(squared) a player can be in order for this + * spawner to be active. + *
+ * If this value is less than or equal to 0, this spawner is always active + * (given that there are players online). + *
+ * Default value is 16. + * + * @return the maximum distance(squared) a player can be in order for this + * spawner to be active. + */ + public int getRequiredPlayerRange(); + + /** + * Set the maximum distance (squared) a player can be in order for this + * spawner to be active. + *
+ * Setting this value to less than or equal to 0 will make this spawner + * always active (given that there are players online). + * + * @param requiredPlayerRange the maximum distance (squared) a player can be + * in order for this spawner to be active. + */ + public void setRequiredPlayerRange(int requiredPlayerRange); + + /** + * Get the radius around which the spawner will attempt to spawn mobs in. + *
+ * This area is square, includes the block the spawner is in, and is + * centered on the spawner's x,z coordinates - not the spawner itself. + *
+ * It is 2 blocks high, centered on the spawner's y-coordinate (its bottom); + * thus allowing mobs to spawn as high as its top surface and as low + * as 1 block below its bottom surface. + *
+ * Default value is 4. + * + * @return the spawn range + */ + public int getSpawnRange(); + + /** + * Set the new spawn range. + *
+ * + * @see #getSpawnRange() + * @param spawnRange the new spawn range + */ + public void setSpawnRange(int spawnRange); +} diff --git a/src/main/java/org/bukkit/block/DaylightDetector.java b/src/main/java/org/bukkit/block/DaylightDetector.java new file mode 100644 index 00000000..ea4117ea --- /dev/null +++ b/src/main/java/org/bukkit/block/DaylightDetector.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of a (possibly inverted) daylight detector. + */ +public interface DaylightDetector extends BlockState { } diff --git a/src/main/java/org/bukkit/block/Dispenser.java b/src/main/java/org/bukkit/block/Dispenser.java new file mode 100644 index 00000000..108332df --- /dev/null +++ b/src/main/java/org/bukkit/block/Dispenser.java @@ -0,0 +1,32 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.projectiles.BlockProjectileSource; + +/** + * Represents a captured state of a dispenser. + */ +public interface Dispenser extends Container, Nameable { + + /** + * Gets the BlockProjectileSource object for the dispenser. + *

+ * If the block represented by this state is no longer a dispenser, this + * will return null. + * + * @return a BlockProjectileSource if valid, otherwise null + * @throws IllegalStateException if this block state is not placed + */ + public BlockProjectileSource getBlockProjectileSource(); + + /** + * Attempts to dispense the contents of the dispenser. + *

+ * If the block represented by this state is no longer a dispenser, this + * will return false. + * + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + */ + public boolean dispense(); +} diff --git a/src/main/java/org/bukkit/block/DoubleChest.java b/src/main/java/org/bukkit/block/DoubleChest.java new file mode 100644 index 00000000..663fcbbd --- /dev/null +++ b/src/main/java/org/bukkit/block/DoubleChest.java @@ -0,0 +1,50 @@ +package org.bukkit.block; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.inventory.DoubleChestInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a double chest. + */ +public class DoubleChest implements InventoryHolder { + private DoubleChestInventory inventory; + + public DoubleChest(DoubleChestInventory chest) { + inventory = chest; + } + + public Inventory getInventory() { + return inventory; + } + + public InventoryHolder getLeftSide() { + return inventory.getLeftSide().getHolder(); + } + + public InventoryHolder getRightSide() { + return inventory.getRightSide().getHolder(); + } + + public Location getLocation() { + return getInventory().getLocation(); + } + + public World getWorld() { + return getLocation().getWorld(); + } + + public double getX() { + return getLocation().getX(); + } + + public double getY() { + return getLocation().getY(); + } + + public double getZ() { + return getLocation().getZ(); + } +} diff --git a/src/main/java/org/bukkit/block/Dropper.java b/src/main/java/org/bukkit/block/Dropper.java new file mode 100644 index 00000000..ba091374 --- /dev/null +++ b/src/main/java/org/bukkit/block/Dropper.java @@ -0,0 +1,31 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; + +/** + * Represents a captured state of a dropper. + */ +public interface Dropper extends Container, Nameable { + + /** + * Tries to drop a randomly selected item from the dropper's inventory, + * following the normal behavior of a dropper. + *

+ * Normal behavior of a dropper is as follows: + *

+ * If the block that the dropper is facing is an InventoryHolder, + * the randomly selected ItemStack is placed within that + * Inventory in the first slot that's available, starting with 0 and + * counting up. If the inventory is full, nothing happens. + *

+ * If the block that the dropper is facing is not an InventoryHolder, + * the randomly selected ItemStack is dropped on + * the ground in the form of an {@link org.bukkit.entity.Item Item}. + *

+ * If the block represented by this state is no longer a dropper, this will + * do nothing. + * + * @throws IllegalStateException if this block state is not placed + */ + public void drop(); +} diff --git a/src/main/java/org/bukkit/block/EnchantingTable.java b/src/main/java/org/bukkit/block/EnchantingTable.java new file mode 100644 index 00000000..9f5aa6c6 --- /dev/null +++ b/src/main/java/org/bukkit/block/EnchantingTable.java @@ -0,0 +1,8 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; + +/** + * Represents a captured state of an enchanting table. + */ +public interface EnchantingTable extends BlockState, Nameable { } diff --git a/src/main/java/org/bukkit/block/EndGateway.java b/src/main/java/org/bukkit/block/EndGateway.java new file mode 100644 index 00000000..a1a6b3b0 --- /dev/null +++ b/src/main/java/org/bukkit/block/EndGateway.java @@ -0,0 +1,46 @@ +package org.bukkit.block; + +import org.bukkit.Location; + +/** + * Represents a captured state of an end gateway. + */ +public interface EndGateway extends BlockState { + + /** + * Gets the location that entities are teleported to when + * entering the gateway portal. + *

+ * If this block state is not placed the location's world will be null. + * + * @return the gateway exit location + */ + Location getExitLocation(); + + /** + * Sets the exit location that entities are teleported to when + * they enter the gateway portal. + *

+ * If this block state is not placed the location's world has to be null. + * + * @param location the new exit location + * @throws IllegalArgumentException for differing worlds + */ + void setExitLocation(Location location); + + /** + * Gets whether this gateway will teleport entities directly to + * the exit location instead of finding a nearby location. + * + * @return true if the gateway is teleporting to the exact location + */ + boolean isExactTeleport(); + + /** + * Sets whether this gateway will teleport entities directly to + * the exit location instead of finding a nearby location. + * + * @param exact whether to teleport to the exact location + */ + void setExactTeleport(boolean exact); +} diff --git a/src/main/java/org/bukkit/block/EnderChest.java b/src/main/java/org/bukkit/block/EnderChest.java new file mode 100644 index 00000000..e0eb2560 --- /dev/null +++ b/src/main/java/org/bukkit/block/EnderChest.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of an ender chest. + */ +public interface EnderChest extends BlockState { } diff --git a/src/main/java/org/bukkit/block/FlowerPot.java b/src/main/java/org/bukkit/block/FlowerPot.java new file mode 100644 index 00000000..5204717a --- /dev/null +++ b/src/main/java/org/bukkit/block/FlowerPot.java @@ -0,0 +1,26 @@ +package org.bukkit.block; + +import org.bukkit.material.MaterialData; + +/** + * Represents a captured state of a flower pot. + */ +public interface FlowerPot extends BlockState { + + /** + * Gets the item present in this flower pot. + * + * @return item present, or null for empty. + */ + MaterialData getContents(); + + /** + * Sets the item present in this flower pot. + * + * NOTE: The Vanilla Minecraft client will currently not refresh this until + * a block update is triggered. + * + * @param item new item, or null for empty. + */ + void setContents(MaterialData item); +} diff --git a/src/main/java/org/bukkit/block/Furnace.java b/src/main/java/org/bukkit/block/Furnace.java new file mode 100644 index 00000000..ee8a9560 --- /dev/null +++ b/src/main/java/org/bukkit/block/Furnace.java @@ -0,0 +1,44 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.inventory.FurnaceInventory; + +/** + * Represents a captured state of a furnace. + */ +public interface Furnace extends Container, Nameable { + + /** + * Get burn time. + * + * @return Burn time + */ + public short getBurnTime(); + + /** + * Set burn time. + * + * @param burnTime Burn time + */ + public void setBurnTime(short burnTime); + + /** + * Get cook time. + * + * @return Cook time + */ + public short getCookTime(); + + /** + * Set cook time. + * + * @param cookTime Cook time + */ + public void setCookTime(short cookTime); + + @Override + public FurnaceInventory getInventory(); + + @Override + public FurnaceInventory getSnapshotInventory(); +} diff --git a/src/main/java/org/bukkit/block/Hopper.java b/src/main/java/org/bukkit/block/Hopper.java new file mode 100644 index 00000000..bc3aeef2 --- /dev/null +++ b/src/main/java/org/bukkit/block/Hopper.java @@ -0,0 +1,8 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; + +/** + * Represents a captured state of a hopper. + */ +public interface Hopper extends Container, Nameable { } diff --git a/src/main/java/org/bukkit/block/Jukebox.java b/src/main/java/org/bukkit/block/Jukebox.java new file mode 100644 index 00000000..e070d874 --- /dev/null +++ b/src/main/java/org/bukkit/block/Jukebox.java @@ -0,0 +1,41 @@ +package org.bukkit.block; + +import org.bukkit.Material; + +/** + * Represents a captured state of a jukebox. + */ +public interface Jukebox extends BlockState { + + /** + * Gets the record being played. + * + * @return The record Material, or AIR if none is playing + */ + public Material getPlaying(); + + /** + * Sets the record being played. + * + * @param record The record Material, or null/AIR to stop playing + */ + public void setPlaying(Material record); + + /** + * Checks if the jukebox is playing a record. + * + * @return True if there is a record playing + */ + public boolean isPlaying(); + + /** + * Stops the jukebox playing and ejects the current record. + *

+ * If the block represented by this state is no longer a jukebox, this will + * do nothing and return false. + * + * @return True if a record was ejected; false if there was none playing + * @throws IllegalStateException if this block state is not placed + */ + public boolean eject(); +} diff --git a/src/main/java/org/bukkit/block/Lockable.java b/src/main/java/org/bukkit/block/Lockable.java new file mode 100644 index 00000000..5e9f7614 --- /dev/null +++ b/src/main/java/org/bukkit/block/Lockable.java @@ -0,0 +1,31 @@ +package org.bukkit.block; + +/** + * Represents a block (usually a container) that may be locked. When a lock is + * active an item with a name corresponding to the key will be required to open + * this block. + */ +public interface Lockable { + + /** + * Checks if the container has a valid (non empty) key. + * + * @return true if the key is valid. + */ + boolean isLocked(); + + /** + * Gets the key needed to access the container. + * + * @return the key needed. + */ + String getLock(); + + /** + * Sets the key required to access this container. Set to null (or empty + * string) to remove key. + * + * @param key the key required to access the container. + */ + void setLock(String key); +} diff --git a/src/main/java/org/bukkit/block/NoteBlock.java b/src/main/java/org/bukkit/block/NoteBlock.java new file mode 100644 index 00000000..2f9ac5c0 --- /dev/null +++ b/src/main/java/org/bukkit/block/NoteBlock.java @@ -0,0 +1,82 @@ +package org.bukkit.block; + +import org.bukkit.Instrument; +import org.bukkit.Note; + +/** + * Represents a captured state of a note block. + */ +public interface NoteBlock extends BlockState { + + /** + * Gets the note. + * + * @return The note. + */ + public Note getNote(); + + /** + * Gets the note. + * + * @return The note ID. + * @deprecated Magic value + */ + @Deprecated + public byte getRawNote(); + + /** + * Set the note. + * + * @param note The note. + */ + public void setNote(Note note); + + /** + * Set the note. + * + * @param note The note ID. + * @deprecated Magic value + */ + @Deprecated + public void setRawNote(byte note); + + /** + * Attempts to play the note at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + */ + public boolean play(); + + /** + * Plays an arbitrary note with an arbitrary instrument at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @param instrument Instrument ID + * @param note Note ID + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + * @deprecated Magic value + */ + @Deprecated + public boolean play(byte instrument, byte note); + + /** + * Plays an arbitrary note with an arbitrary instrument at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @param instrument The instrument + * @param note The note + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + * @see Instrument Note + */ + public boolean play(Instrument instrument, Note note); +} diff --git a/src/main/java/org/bukkit/block/PistonMoveReaction.java b/src/main/java/org/bukkit/block/PistonMoveReaction.java new file mode 100644 index 00000000..3df37d0d --- /dev/null +++ b/src/main/java/org/bukkit/block/PistonMoveReaction.java @@ -0,0 +1,66 @@ +package org.bukkit.block; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents how a block or entity will react when interacting with a piston + * when it is extending or retracting. + */ +public enum PistonMoveReaction { + + /** + * Indicates that the block can be pushed or pulled. + */ + MOVE(0), + /** + * Indicates the block is fragile and will break if pushed on. + */ + BREAK(1), + /** + * Indicates that the block will resist being pushed or pulled. + */ + BLOCK(2), + /** + * Indicates that the entity will ignore any interaction(s) with + * pistons. + *
+ * Blocks should use {@link PistonMoveReaction#BLOCK}. + */ + IGNORE(3), + /** + * Indicates that the block can only be pushed by pistons, not pulled. + */ + PUSH_ONLY(4); + + private int id; + private static Map byId = new HashMap(); + static { + for (PistonMoveReaction reaction : PistonMoveReaction.values()) { + byId.put(reaction.id, reaction); + } + } + + private PistonMoveReaction(int id) { + this.id = id; + } + + /** + * @return The ID of the move reaction + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return this.id; + } + + /** + * @param id An ID + * @return The move reaction with that ID + * @deprecated Magic value + */ + @Deprecated + public static PistonMoveReaction getById(int id) { + return byId.get(id); + } +} diff --git a/src/main/java/org/bukkit/block/ShulkerBox.java b/src/main/java/org/bukkit/block/ShulkerBox.java new file mode 100644 index 00000000..4c1740e7 --- /dev/null +++ b/src/main/java/org/bukkit/block/ShulkerBox.java @@ -0,0 +1,17 @@ +package org.bukkit.block; + +import org.bukkit.DyeColor; +import org.bukkit.Nameable; + +/** + * Represents a captured state of a ShulkerBox. + */ +public interface ShulkerBox extends Container, Nameable { + + /** + * Get the {@link DyeColor} corresponding to this ShulkerBox + * + * @return the {@link DyeColor} of this ShulkerBox + */ + public DyeColor getColor(); +} diff --git a/src/main/java/org/bukkit/block/Sign.java b/src/main/java/org/bukkit/block/Sign.java new file mode 100644 index 00000000..f30666a4 --- /dev/null +++ b/src/main/java/org/bukkit/block/Sign.java @@ -0,0 +1,37 @@ +package org.bukkit.block; + +/** + * Represents a captured state of either a SignPost or a WallSign. + */ +public interface Sign extends BlockState { + + /** + * Gets all the lines of text currently on this sign. + * + * @return Array of Strings containing each line of text + */ + public String[] getLines(); + + /** + * Gets the line of text at the specified index. + *

+ * For example, getLine(0) will return the first line of text. + * + * @param index Line number to get the text from, starting at 0 + * @throws IndexOutOfBoundsException Thrown when the line does not exist + * @return Text on the given line + */ + public String getLine(int index) throws IndexOutOfBoundsException; + + /** + * Sets the line of text at the specified index. + *

+ * For example, setLine(0, "Line One") will set the first line of text to + * "Line One". + * + * @param index Line number to set the text at, starting from 0 + * @param line New text to set at the specified index + * @throws IndexOutOfBoundsException If the index is out of the range 0..3 + */ + public void setLine(int index, String line) throws IndexOutOfBoundsException; +} diff --git a/src/main/java/org/bukkit/block/Skull.java b/src/main/java/org/bukkit/block/Skull.java new file mode 100644 index 00000000..e7cfcaac --- /dev/null +++ b/src/main/java/org/bukkit/block/Skull.java @@ -0,0 +1,83 @@ +package org.bukkit.block; + +import org.bukkit.OfflinePlayer; +import org.bukkit.SkullType; + +/** + * Represents a captured state of a skull block. + */ +public interface Skull extends BlockState { + + /** + * Checks to see if the skull has an owner + * + * @return true if the skull has an owner + */ + public boolean hasOwner(); + + /** + * Gets the owner of the skull, if one exists + * + * @return the owner of the skull or null if the skull does not have an owner + * @deprecated See {@link #getOwningPlayer()}. + */ + @Deprecated + public String getOwner(); + + /** + * Sets the owner of the skull + *

+ * Involves a potentially blocking web request to acquire the profile data for + * the provided name. + * + * @param name the new owner of the skull + * @return true if the owner was successfully set + * @deprecated see {@link #setOwningPlayer(OfflinePlayer)}. + */ + @Deprecated + public boolean setOwner(String name); + + /** + * Get the player which owns the skull. This player may appear as the + * texture depending on skull type. + * + * @return owning player + */ + public OfflinePlayer getOwningPlayer(); + + /** + * Set the player which owns the skull. This player may appear as the + * texture depending on skull type. + * + * @param player the owning player + */ + public void setOwningPlayer(OfflinePlayer player); + + /** + * Gets the rotation of the skull in the world + * + * @return the rotation of the skull + */ + public BlockFace getRotation(); + + /** + * Sets the rotation of the skull in the world + * + * @param rotation the rotation of the skull + */ + public void setRotation(BlockFace rotation); + + /** + * Gets the type of skull + * + * @return the type of skull + */ + public SkullType getSkullType(); + + /** + * Sets the type of skull + * + * @param skullType the type of skull + */ + public void setSkullType(SkullType skullType); +} diff --git a/src/main/java/org/bukkit/block/Structure.java b/src/main/java/org/bukkit/block/Structure.java new file mode 100644 index 00000000..eb7a5363 --- /dev/null +++ b/src/main/java/org/bukkit/block/Structure.java @@ -0,0 +1,234 @@ +package org.bukkit.block; + +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.block.structure.UsageMode; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.BlockVector; + +/** + * Represents a structure block that can save and load blocks from a file. They + * can only be used by OPs, and are not obtainable in survival. + */ +public interface Structure extends BlockState { + + /** + * The name of this structure. + * + * @return structure name + */ + String getStructureName(); + + /** + * Set the name of this structure. This is case-sensitive. The name of the + * structure in the {@link UsageMode#SAVE} structure block MUST match the + * name within the {@link UsageMode#CORNER} block or the size calculation + * will fail. + * + * @param name the case-sensitive name of this structure + */ + void setStructureName(String name); + + /** + * Get the name of who created this structure. + * + * @return the name of whoever created this structure. + */ + String getAuthor(); + + /** + * Set the name of whoever created this structure. + * + * @param author whoever created this structure + */ + void setAuthor(String author); + + /** + * Set the name of whoever created this structure using a + * {@link LivingEntity}. + * + * @param livingEntity the entity who created this structure + */ + void setAuthor(LivingEntity livingEntity); + + /** + * The relative position of the structure outline based on the position of + * the structure block. Maximum allowed distance is 32 blocks in any + * direction. + * + * @return a Location which contains the relative distance this structure is + * from the structure block. + */ + BlockVector getRelativePosition(); + + /** + * Set the relative position from the structure block. Maximum allowed + * distance is 32 blocks in any direction. + * + * @param vector the {@link BlockVector} containing the relative origin + * coordinates of this structure. + */ + void setRelativePosition(BlockVector vector); + + /** + * The distance to the opposite corner of this structure. The maximum + * structure size is 32x32x32. When a structure has successfully been + * calculated (i.e. it is within the maximum allowed distance) a white + * border surrounds the structure. + * + * @return a {@link BlockVector} which contains the total size of the + * structure. + */ + BlockVector getStructureSize(); + + /** + * Set the maximum size of this structure from the origin point. Maximum + * allowed size is 32x32x32. + * + * @param vector the {@link BlockVector} containing the size of this + * structure, based off of the origin coordinates. + */ + void setStructureSize(BlockVector vector); + + /** + * Sets the mirroring of the structure. + * + * @param mirror the new mirroring method + */ + void setMirror(Mirror mirror); + + /** + * How this structure is mirrored. + * + * @return the current mirroring method + */ + Mirror getMirror(); + + /** + * Set how this structure is rotated. + * + * @param rotation the new rotation + */ + void setRotation(StructureRotation rotation); + + /** + * Get how this structure is rotated. + * + * @return the new rotation + */ + StructureRotation getRotation(); + + /** + * Set the {@link UsageMode} of this structure block. + * + * @param mode the new mode to set. + */ + void setUsageMode(UsageMode mode); + + /** + * Get the {@link UsageMode} of this structure block. + * + * @return the mode this block is currently in. + */ + UsageMode getUsageMode(); + + /** + * While in {@link UsageMode#SAVE} mode, this will ignore any entities when + * saving the structure. + *
+ * While in {@link UsageMode#LOAD} mode this will ignore any entities that + * were saved to file. + * + * @param ignoreEntities the flag to set + */ + void setIgnoreEntities(boolean ignoreEntities); + + /** + * Get if this structure block should ignore entities. + * + * @return true if the appropriate {@link UsageMode} should ignore entities. + */ + boolean isIgnoreEntities(); + + /** + * Set if the structure outline should show air blocks. + * + * @param showAir if the structure block should show air blocks + */ + void setShowAir(boolean showAir); + + /** + * Check if this structure block is currently showing all air blocks + * + * @return true if the structure block is showing all air blocks + */ + boolean isShowAir(); + + /** + * Set if this structure box should show the bounding box. + * + * @param showBoundingBox if the structure box should be shown + */ + void setBoundingBoxVisible(boolean showBoundingBox); + + /** + * Get if this structure block is currently showing the bounding box. + * + * @return true if the bounding box is shown + */ + boolean isBoundingBoxVisible(); + + /** + * Set the integrity of the structure. Integrity must be between 0.0 and 1.0 + * Lower integrity values will result in more blocks being removed when + * loading a structure. Integrity and {@link #getSeed()} are used together + * to determine which blocks are randomly removed to mimic "decay." + * + * @param integrity the integrity of this structure + */ + void setIntegrity(float integrity); + + /** + * Get the integrity of this structure. + * + * @return the integrity of this structure + */ + float getIntegrity(); + + /** + * The seed used to determine which blocks will be removed upon loading. + * {@link #getIntegrity()} and seed are used together to determine which + * blocks are randomly removed to mimic "decay." + * + * @param seed the seed used to determine how many blocks will be removed + */ + void setSeed(long seed); + + /** + * The seed used to determine how many blocks are removed upon loading of + * this structure. + * + * @return the seed used + */ + long getSeed(); + + /** + * Only applicable while in {@link UsageMode#DATA}. Metadata are specific + * functions that can be applied to the structure location. Consult the + * Minecraft + * wiki for more information. + * + * @param metadata the function to perform on the selected location + */ + void setMetadata(String metadata); + + /** + * Get the metadata function this structure block will perform when + * activated. Consult the + * Minecraft + * Wiki for more information. + * + * @return the function that will be performed when this block is activated + */ + String getMetadata(); +} diff --git a/src/main/java/org/bukkit/block/banner/Pattern.java b/src/main/java/org/bukkit/block/banner/Pattern.java new file mode 100644 index 00000000..4a8444d2 --- /dev/null +++ b/src/main/java/org/bukkit/block/banner/Pattern.java @@ -0,0 +1,94 @@ +package org.bukkit.block.banner; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.NoSuchElementException; +import org.bukkit.DyeColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +@SerializableAs("Pattern") +public class Pattern implements ConfigurationSerializable { + + private static final String COLOR = "color"; + private static final String PATTERN = "pattern"; + + private final DyeColor color; + private final PatternType pattern; + + /** + * Creates a new pattern from the specified color and + * pattern type + * + * @param color the pattern color + * @param pattern the pattern type + */ + public Pattern(DyeColor color, PatternType pattern) { + this.color = color; + this.pattern = pattern; + } + + /** + * Constructor for deserialization. + * + * @param map the map to deserialize from + */ + public Pattern(Map map) { + color = DyeColor.valueOf(getString(map, COLOR)); + pattern = PatternType.getByIdentifier(getString(map, PATTERN)); + } + + private static String getString(Map map, Object key) { + Object str = map.get(key); + if (str instanceof String) { + return (String) str; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + @Override + public Map serialize() { + return ImmutableMap.of( + COLOR, color.toString(), + PATTERN, pattern.getIdentifier() + ); + } + + /** + * Returns the color of the pattern + * + * @return the color of the pattern + */ + public DyeColor getColor() { + return color; + } + + /** + * Returns the type of pattern + * + * @return the pattern type + */ + public PatternType getPattern() { + return pattern; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.color != null ? this.color.hashCode() : 0); + hash = 97 * hash + (this.pattern != null ? this.pattern.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pattern other = (Pattern) obj; + return this.color == other.color && this.pattern == other.pattern; + } +} diff --git a/src/main/java/org/bukkit/block/banner/PatternType.java b/src/main/java/org/bukkit/block/banner/PatternType.java new file mode 100644 index 00000000..25543b0b --- /dev/null +++ b/src/main/java/org/bukkit/block/banner/PatternType.java @@ -0,0 +1,80 @@ +package org.bukkit.block.banner; + +import java.util.HashMap; +import java.util.Map; + +public enum PatternType { + BASE("b"), + SQUARE_BOTTOM_LEFT("bl"), + SQUARE_BOTTOM_RIGHT("br"), + SQUARE_TOP_LEFT("tl"), + SQUARE_TOP_RIGHT("tr"), + STRIPE_BOTTOM("bs"), + STRIPE_TOP("ts"), + STRIPE_LEFT("ls"), + STRIPE_RIGHT("rs"), + STRIPE_CENTER("cs"), + STRIPE_MIDDLE("ms"), + STRIPE_DOWNRIGHT("drs"), + STRIPE_DOWNLEFT("dls"), + STRIPE_SMALL("ss"), + CROSS("cr"), + STRAIGHT_CROSS("sc"), + TRIANGLE_BOTTOM("bt"), + TRIANGLE_TOP("tt"), + TRIANGLES_BOTTOM("bts"), + TRIANGLES_TOP("tts"), + DIAGONAL_LEFT("ld"), + DIAGONAL_RIGHT("rd"), + DIAGONAL_LEFT_MIRROR("lud"), + DIAGONAL_RIGHT_MIRROR("rud"), + CIRCLE_MIDDLE("mc"), + RHOMBUS_MIDDLE("mr"), + HALF_VERTICAL("vh"), + HALF_HORIZONTAL("hh"), + HALF_VERTICAL_MIRROR("vhr"), + HALF_HORIZONTAL_MIRROR("hhb"), + BORDER("bo"), + CURLY_BORDER("cbo"), + CREEPER("cre"), + GRADIENT("gra"), + GRADIENT_UP("gru"), + BRICKS("bri"), + SKULL("sku"), + FLOWER("flo"), + MOJANG("moj"); + + private final String identifier; + private static final Map byString = new HashMap(); + + static { + for (PatternType p : values()) { + byString.put(p.identifier, p); + } + } + + private PatternType(String key) { + this.identifier = key; + } + + /** + * Returns the identifier used to represent + * this pattern type + * + * @return the pattern's identifier + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns the pattern type which matches the passed + * identifier or null if no matches are found + * + * @param identifier the identifier + * @return the matched pattern type or null + */ + public static PatternType getByIdentifier(String identifier) { + return byString.get(identifier); + } +} diff --git a/src/main/java/org/bukkit/block/structure/Mirror.java b/src/main/java/org/bukkit/block/structure/Mirror.java new file mode 100644 index 00000000..86e812a1 --- /dev/null +++ b/src/main/java/org/bukkit/block/structure/Mirror.java @@ -0,0 +1,27 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be mirrored upon + * being loaded. + */ +public enum Mirror { + + /** + * No mirroring. + *
+ * Positive X to Positive Z + */ + NONE, + /** + * Structure is mirrored left to right. + *
+ * Similar to looking in a mirror. Positive X to Negative Z + */ + LEFT_RIGHT, + /** + * Structure is mirrored front to back. + *
+ * Positive Z to Negative X + */ + FRONT_BACK; +} diff --git a/src/main/java/org/bukkit/block/structure/StructureRotation.java b/src/main/java/org/bukkit/block/structure/StructureRotation.java new file mode 100644 index 00000000..0d0bfca4 --- /dev/null +++ b/src/main/java/org/bukkit/block/structure/StructureRotation.java @@ -0,0 +1,26 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be rotated. + */ +public enum StructureRotation { + + /** + * No rotation. + */ + NONE, + /** + * Rotated clockwise 90 degrees. + */ + CLOCKWISE_90, + /** + * Rotated clockwise 180 degrees. + */ + CLOCKWISE_180, + /** + * Rotated counter clockwise 90 degrees. + *
+ * Equivalent to rotating clockwise 270 degrees. + */ + COUNTERCLOCKWISE_90; +} diff --git a/src/main/java/org/bukkit/block/structure/UsageMode.java b/src/main/java/org/bukkit/block/structure/UsageMode.java new file mode 100644 index 00000000..cbea3f38 --- /dev/null +++ b/src/main/java/org/bukkit/block/structure/UsageMode.java @@ -0,0 +1,29 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be used. + */ +public enum UsageMode { + + /** + * The mode used when saving a structure. + */ + SAVE, + /** + * The mode used when loading a structure. + */ + LOAD, + /** + * Used when saving a structure for easy size calculation. When using this + * mode, the Structure name MUST match the name in the second Structure + * block that is in {@link UsageMode#SAVE}. + */ + CORNER, + /** + * Used to run specific custom functions, which can only be used for certain + * Structures. The structure block is removed after this function completes. + * The data tags (functions) can be found on the + * wiki. + */ + DATA; +} diff --git a/src/main/java/org/bukkit/boss/BarColor.java b/src/main/java/org/bukkit/boss/BarColor.java new file mode 100644 index 00000000..e191d9ff --- /dev/null +++ b/src/main/java/org/bukkit/boss/BarColor.java @@ -0,0 +1,11 @@ +package org.bukkit.boss; + +public enum BarColor { + PINK, + BLUE, + RED, + GREEN, + YELLOW, + PURPLE, + WHITE +} diff --git a/src/main/java/org/bukkit/boss/BarFlag.java b/src/main/java/org/bukkit/boss/BarFlag.java new file mode 100644 index 00000000..69e02998 --- /dev/null +++ b/src/main/java/org/bukkit/boss/BarFlag.java @@ -0,0 +1,17 @@ +package org.bukkit.boss; + +public enum BarFlag { + + /** + * Darkens the sky like during fighting a wither. + */ + DARKEN_SKY, + /** + * Tells the client to play the Ender Dragon boss music. + */ + PLAY_BOSS_MUSIC, + /** + * Creates fog around the world. + */ + CREATE_FOG, +} diff --git a/src/main/java/org/bukkit/boss/BarStyle.java b/src/main/java/org/bukkit/boss/BarStyle.java new file mode 100644 index 00000000..3e499eb7 --- /dev/null +++ b/src/main/java/org/bukkit/boss/BarStyle.java @@ -0,0 +1,24 @@ +package org.bukkit.boss; + +public enum BarStyle { + /** + * Makes the boss bar solid (no segments) + */ + SOLID, + /** + * Splits the boss bar into 6 segments + */ + SEGMENTED_6, + /** + * Splits the boss bar into 10 segments + */ + SEGMENTED_10, + /** + * Splits the boss bar into 12 segments + */ + SEGMENTED_12, + /** + * Splits the boss bar into 20 segments + */ + SEGMENTED_20, +} diff --git a/src/main/java/org/bukkit/boss/BossBar.java b/src/main/java/org/bukkit/boss/BossBar.java new file mode 100644 index 00000000..effc3295 --- /dev/null +++ b/src/main/java/org/bukkit/boss/BossBar.java @@ -0,0 +1,144 @@ +package org.bukkit.boss; + +import org.bukkit.entity.Player; + +import java.util.List; + +public interface BossBar { + + /** + * Returns the title of this boss bar + * + * @return the title of the bar + */ + String getTitle(); + + /** + * Sets the title of this boss bar + * + * @param title the title of the bar + */ + void setTitle(String title); + + /** + * Returns the color of this boss bar + * + * @return the color of the bar + */ + BarColor getColor(); + + /** + * Sets the color of this boss bar. + * + * @param color the color of the bar + */ + void setColor(BarColor color); + + /** + * Returns the style of this boss bar + * + * @return the style of the bar + */ + BarStyle getStyle(); + + /** + * Sets the bar style of this boss bar + * + * @param style the style of the bar + */ + void setStyle(BarStyle style); + + /** + * Remove an existing flag on this boss bar + * + * @param flag the existing flag to remove + */ + void removeFlag(BarFlag flag); + + /** + * Add an optional flag to this boss bar + * + * @param flag an optional flag to set on the boss bar + */ + void addFlag(BarFlag flag); + + /** + * Returns whether this boss bar as the passed flag set + * + * @param flag the flag to check + * @return whether it has the flag + */ + boolean hasFlag(BarFlag flag); + + /** + * Sets the progress of the bar. Values should be between 0.0 (empty) and + * 1.0 (full) + * + * @param progress the progress of the bar + */ + void setProgress(double progress); + + /** + * Returns the progress of the bar between 0.0 and 1.0 + * + * @return the progress of the bar + */ + double getProgress(); + + /** + * Adds the player to this boss bar causing it to display on their screen. + * + * @param player the player to add + */ + void addPlayer(Player player); + + /** + * Removes the player from this boss bar causing it to be removed from their + * screen. + * + * @param player the player to remove + */ + void removePlayer(Player player); + + /** + * Removes all players from this boss bar + * + * @see #removePlayer(Player) + */ + void removeAll(); + + /** + * Returns all players viewing this boss bar + * + * @return a immutable list of players + */ + List getPlayers(); + + /** + * Set if the boss bar is displayed to attached players. + * + * @param visible visible status + */ + void setVisible(boolean visible); + + /** + * Return if the boss bar is displayed to attached players. + * + * @return visible status + */ + boolean isVisible(); + + /** + * Shows the previously hidden boss bar to all attached players + * @deprecated {@link #setVisible(boolean)} + */ + @Deprecated + void show(); + + /** + * Hides this boss bar from all attached players + * @deprecated {@link #setVisible(boolean)} + */ + @Deprecated + void hide(); +} diff --git a/src/main/java/org/bukkit/command/BlockCommandSender.java b/src/main/java/org/bukkit/command/BlockCommandSender.java new file mode 100644 index 00000000..ce229d24 --- /dev/null +++ b/src/main/java/org/bukkit/command/BlockCommandSender.java @@ -0,0 +1,13 @@ +package org.bukkit.command; + +import org.bukkit.block.Block; + +public interface BlockCommandSender extends CommandSender { + + /** + * Returns the block this command sender belongs to + * + * @return Block for the command sender + */ + public Block getBlock(); +} diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java new file mode 100644 index 00000000..c04ec003 --- /dev/null +++ b/src/main/java/org/bukkit/command/Command.java @@ -0,0 +1,432 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.util.StringUtil; + +import com.google.common.collect.ImmutableList; + +/** + * Represents a Command, which executes various tasks upon user input + */ +public abstract class Command { + private String name; + private String nextLabel; + private String label; + private List aliases; + private List activeAliases; + private CommandMap commandMap = null; + protected String description = ""; + protected String usageMessage; + private String permission; + private String permissionMessage; + public org.spigotmc.CustomTimingsHandler timings; // Spigot + + protected Command(String name) { + this(name, "", "/" + name, new ArrayList()); + } + + protected Command(String name, String description, String usageMessage, List aliases) { + this.name = name; + this.nextLabel = name; + this.label = name; + this.description = description; + this.usageMessage = usageMessage; + this.aliases = aliases; + this.activeAliases = new ArrayList(aliases); + this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot + } + + /** + * Executes the command, returning its success + * + * @param sender Source object which is executing this command + * @param commandLabel The alias of the command used + * @param args All arguments passed to the command, split via ' ' + * @return true if the command was successful, otherwise false + */ + public abstract boolean execute(CommandSender sender, String commandLabel, String[] args); + + /** + * Executed on tab completion for this command, returning a list of + * options the player can tab through. + * + * @param sender Source object which is executing this command + * @param alias the alias being used + * @param args All arguments passed to the command, split via ' ' + * @return a list of tab-completions for the specified arguments. This + * will never be null. List may be immutable. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return tabComplete0(sender, alias, args, null); + } + + /** + * Executed on tab completion for this command, returning a list of + * options the player can tab through. + * + * @param sender Source object which is executing this command + * @param alias the alias being used + * @param args All arguments passed to the command, split via ' ' + * @param location The position looked at by the sender, or null if none + * @return a list of tab-completions for the specified arguments. This + * will never be null. List may be immutable. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + return tabComplete(sender, alias, args); + } + + private List tabComplete0(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 0) { + return ImmutableList.of(); + } + + String lastWord = args[args.length - 1]; + + Player senderPlayer = sender instanceof Player ? (Player) sender : null; + + ArrayList matchedPlayers = new ArrayList(); + for (Player player : sender.getServer().getOnlinePlayers()) { + String name = player.getName(); + if ((senderPlayer == null || senderPlayer.canSee(player)) && StringUtil.startsWithIgnoreCase(name, lastWord)) { + matchedPlayers.add(name); + } + } + + Collections.sort(matchedPlayers, String.CASE_INSENSITIVE_ORDER); + return matchedPlayers; + } + + /** + * Returns the name of this command + * + * @return Name of this command + */ + public String getName() { + return name; + } + + /** + * Sets the name of this command. + *

+ * May only be used before registering the command. + * Will return true if the new name is set, and false + * if the command has already been registered. + * + * @param name New command name + * @return returns true if the name change happened instantly or false if + * the command was already registered + */ + public boolean setName(String name) { + if (!isRegistered()) { + this.name = name; + return true; + } + return false; + } + + /** + * Gets the permission required by users to be able to perform this + * command + * + * @return Permission name, or null if none + */ + public String getPermission() { + return permission; + } + + /** + * Sets the permission required by users to be able to perform this + * command + * + * @param permission Permission name or null + */ + public void setPermission(String permission) { + this.permission = permission; + } + + /** + * Tests the given {@link CommandSender} to see if they can perform this + * command. + *

+ * If they do not have permission, they will be informed that they cannot + * do this. + * + * @param target User to test + * @return true if they can use it, otherwise false + */ + public boolean testPermission(CommandSender target) { + if (testPermissionSilent(target)) { + return true; + } + + if (permissionMessage == null) { + target.sendMessage(ChatColor.RED + "I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error."); + } else if (permissionMessage.length() != 0) { + for (String line : permissionMessage.replace("", permission).split("\n")) { + target.sendMessage(line); + } + } + + return false; + } + + /** + * Tests the given {@link CommandSender} to see if they can perform this + * command. + *

+ * No error is sent to the sender. + * + * @param target User to test + * @return true if they can use it, otherwise false + */ + public boolean testPermissionSilent(CommandSender target) { + if ((permission == null) || (permission.length() == 0)) { + return true; + } + + for (String p : permission.split(";")) { + if (target.hasPermission(p)) { + return true; + } + } + + return false; + } + + /** + * Returns the label for this command + * + * @return Label of this command + */ + public String getLabel() { + return label; + } + + /** + * Sets the label of this command. + *

+ * May only be used before registering the command. + * Will return true if the new name is set, and false + * if the command has already been registered. + * + * @param name The command's name + * @return returns true if the name change happened instantly or false if + * the command was already registered + */ + public boolean setLabel(String name) { + this.nextLabel = name; + if (!isRegistered()) { + this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot + this.label = name; + return true; + } + return false; + } + + /** + * Registers this command to a CommandMap. + * Once called it only allows changes the registered CommandMap + * + * @param commandMap the CommandMap to register this command to + * @return true if the registration was successful (the current registered + * CommandMap was the passed CommandMap or null) false otherwise + */ + public boolean register(CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = commandMap; + return true; + } + + return false; + } + + /** + * Unregisters this command from the passed CommandMap applying any + * outstanding changes + * + * @param commandMap the CommandMap to unregister + * @return true if the unregistration was successful (the current + * registered CommandMap was the passed CommandMap or null) false + * otherwise + */ + public boolean unregister(CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = null; + this.activeAliases = new ArrayList(this.aliases); + this.label = this.nextLabel; + return true; + } + + return false; + } + + private boolean allowChangesFrom(CommandMap commandMap) { + return (null == this.commandMap || this.commandMap == commandMap); + } + + /** + * Returns the current registered state of this command + * + * @return true if this command is currently registered false otherwise + */ + public boolean isRegistered() { + return (null != this.commandMap); + } + + /** + * Returns a list of active aliases of this command + * + * @return List of aliases + */ + public List getAliases() { + return activeAliases; + } + + /** + * Returns a message to be displayed on a failed permission check for this + * command + * + * @return Permission check failed message + */ + public String getPermissionMessage() { + return permissionMessage; + } + + /** + * Gets a brief description of this command + * + * @return Description of this command + */ + public String getDescription() { + return description; + } + + /** + * Gets an example usage of this command + * + * @return One or more example usages + */ + public String getUsage() { + return usageMessage; + } + + /** + * Sets the list of aliases to request on registration for this command. + * This is not effective outside of defining aliases in the {@link + * PluginDescriptionFile#getCommands()} (under the + * `aliases' node) is equivalent to this method. + * + * @param aliases aliases to register to this command + * @return this command object, for chaining + */ + public Command setAliases(List aliases) { + this.aliases = aliases; + if (!isRegistered()) { + this.activeAliases = new ArrayList(aliases); + } + return this; + } + + /** + * Sets a brief description of this command. Defining a description in the + * {@link PluginDescriptionFile#getCommands()} (under the + * `description' node) is equivalent to this method. + * + * @param description new command description + * @return this command object, for chaining + */ + public Command setDescription(String description) { + this.description = description; + return this; + } + + /** + * Sets the message sent when a permission check fails + * + * @param permissionMessage new permission message, null to indicate + * default message, or an empty string to indicate no message + * @return this command object, for chaining + */ + public Command setPermissionMessage(String permissionMessage) { + this.permissionMessage = permissionMessage; + return this; + } + + /** + * Sets the example usage of this command + * + * @param usage new example usage + * @return this command object, for chaining + */ + public Command setUsage(String usage) { + this.usageMessage = usage; + return this; + } + + public static void broadcastCommandMessage(CommandSender source, String message) { + broadcastCommandMessage(source, message, true); + } + + public static void broadcastCommandMessage(CommandSender source, String message, boolean sendToSource) { + String result = source.getName() + ": " + message; + + if (source instanceof BlockCommandSender) { + BlockCommandSender blockCommandSender = (BlockCommandSender) source; + + if (blockCommandSender.getBlock().getWorld().getGameRuleValue("commandBlockOutput").equalsIgnoreCase("false")) { + Bukkit.getConsoleSender().sendMessage(result); + return; + } + } else if (source instanceof CommandMinecart) { + CommandMinecart commandMinecart = (CommandMinecart) source; + + if (commandMinecart.getWorld().getGameRuleValue("commandBlockOutput").equalsIgnoreCase("false")) { + Bukkit.getConsoleSender().sendMessage(result); + return; + } + } + + Set users = Bukkit.getPluginManager().getPermissionSubscriptions(Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + String colored = ChatColor.GRAY + "" + ChatColor.ITALIC + "[" + result + ChatColor.GRAY + ChatColor.ITALIC + "]"; + + if (sendToSource && !(source instanceof ConsoleCommandSender)) { + source.sendMessage(message); + } + + for (Permissible user : users) { + if (user instanceof CommandSender && user.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE)) { + CommandSender target = (CommandSender) user; + + if (target instanceof ConsoleCommandSender) { + target.sendMessage(result); + } else if (target != source) { + target.sendMessage(colored); + } + } + } + } + + @Override + public String toString() { + return getClass().getName() + '(' + name + ')'; + } +} diff --git a/src/main/java/org/bukkit/command/CommandException.java b/src/main/java/org/bukkit/command/CommandException.java new file mode 100644 index 00000000..b63015f4 --- /dev/null +++ b/src/main/java/org/bukkit/command/CommandException.java @@ -0,0 +1,28 @@ +package org.bukkit.command; + +/** + * Thrown when an unhandled exception occurs during the execution of a Command + */ +@SuppressWarnings("serial") +public class CommandException extends RuntimeException { + + /** + * Creates a new instance of CommandException without detail + * message. + */ + public CommandException() {} + + /** + * Constructs an instance of CommandException with the + * specified detail message. + * + * @param msg the detail message. + */ + public CommandException(String msg) { + super(msg); + } + + public CommandException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/bukkit/command/CommandExecutor.java b/src/main/java/org/bukkit/command/CommandExecutor.java new file mode 100644 index 00000000..c75586f7 --- /dev/null +++ b/src/main/java/org/bukkit/command/CommandExecutor.java @@ -0,0 +1,18 @@ +package org.bukkit.command; + +/** + * Represents a class which contains a single method for executing commands + */ +public interface CommandExecutor { + + /** + * Executes the given command, returning its success + * + * @param sender Source of the command + * @param command Command which was executed + * @param label Alias of the command which was used + * @param args Passed command arguments + * @return true if a valid command, otherwise false + */ + public boolean onCommand(CommandSender sender, Command command, String label, String[] args); +} diff --git a/src/main/java/org/bukkit/command/CommandMap.java b/src/main/java/org/bukkit/command/CommandMap.java new file mode 100644 index 00000000..30d60247 --- /dev/null +++ b/src/main/java/org/bukkit/command/CommandMap.java @@ -0,0 +1,126 @@ +package org.bukkit.command; + +import java.util.List; +import org.bukkit.Location; + +public interface CommandMap { + + /** + * Registers all the commands belonging to a certain plugin. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param fallbackPrefix a prefix which is prepended to each command with + * a ':' one or more times to make the command unique + * @param commands a list of commands to register + */ + public void registerAll(String fallbackPrefix, List commands); + + /** + * Registers a command. Returns true on success; false if name is already + * taken and fallback had to be used. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param label the label of the command, without the '/'-prefix. + * @param fallbackPrefix a prefix which is prepended to the command with a + * ':' one or more times to make the command unique + * @param command the command to register + * @return true if command was registered with the passed in label, false + * otherwise, which indicates the fallbackPrefix was used one or more + * times + */ + public boolean register(String label, String fallbackPrefix, Command command); + + /** + * Registers a command. Returns true on success; false if name is already + * taken and fallback had to be used. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param fallbackPrefix a prefix which is prepended to the command with a + * ':' one or more times to make the command unique + * @param command the command to register, from which label is determined + * from the command name + * @return true if command was registered with the passed in label, false + * otherwise, which indicates the fallbackPrefix was used one or more + * times + */ + public boolean register(String fallbackPrefix, Command command); + + /** + * Looks for the requested command and executes it if found. + * + * @param sender The command's sender + * @param cmdLine command + arguments. Example: "/test abc 123" + * @return returns false if no target is found, true otherwise. + * @throws CommandException Thrown when the executor for the given command + * fails with an unhandled exception + */ + public boolean dispatch(CommandSender sender, String cmdLine) throws CommandException; + + /** + * Clears all registered commands. + */ + public void clearCommands(); + + /** + * Gets the command registered to the specified name + * + * @param name Name of the command to retrieve + * @return Command with the specified name or null if a command with that + * label doesn't exist + */ + public Command getCommand(String name); + + /** + * Looks for the requested command and executes an appropriate + * tab-completer if found. This method will also tab-complete partial + * commands. + * + * @param sender The command's sender. + * @param cmdLine The entire command string to tab-complete, excluding + * initial slash. + * @return a list of possible tab-completions. This list may be immutable. + * Will be null if no matching command of which sender has permission. + * @throws CommandException Thrown when the tab-completer for the given + * command fails with an unhandled exception + * @throws IllegalArgumentException if either sender or cmdLine are null + */ + public List tabComplete(CommandSender sender, String cmdLine) throws IllegalArgumentException; + + /** + * Looks for the requested command and executes an appropriate + * tab-completer if found. This method will also tab-complete partial + * commands. + * + * @param sender The command's sender. + * @param cmdLine The entire command string to tab-complete, excluding + * initial slash. + * @param location The position looked at by the sender, or null if none + * @return a list of possible tab-completions. This list may be immutable. + * Will be null if no matching command of which sender has permission. + * @throws CommandException Thrown when the tab-completer for the given + * command fails with an unhandled exception + * @throws IllegalArgumentException if either sender or cmdLine are null + */ + public List tabComplete(CommandSender sender, String cmdLine, Location location) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/command/CommandSender.java b/src/main/java/org/bukkit/command/CommandSender.java new file mode 100644 index 00000000..abf68a2c --- /dev/null +++ b/src/main/java/org/bukkit/command/CommandSender.java @@ -0,0 +1,61 @@ +package org.bukkit.command; + +import org.bukkit.Server; +import org.bukkit.permissions.Permissible; + +public interface CommandSender extends Permissible { + + /** + * Sends this sender a message + * + * @param message Message to be displayed + */ + public void sendMessage(String message); + + /** + * Sends this sender multiple messages + * + * @param messages An array of messages to be displayed + */ + public void sendMessage(String[] messages); + + /** + * Returns the server instance that this command is running on + * + * @return Server instance + */ + public Server getServer(); + + /** + * Gets the name of this command sender + * + * @return Name of the sender + */ + public String getName(); + + // Spigot start + public class Spigot + { + + /** + * Sends this sender a chat component. + * + * @param component the components to send + */ + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the sender. + * + * @param components the components to send + */ + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/command/ConsoleCommandSender.java b/src/main/java/org/bukkit/command/ConsoleCommandSender.java new file mode 100644 index 00000000..f309c2ed --- /dev/null +++ b/src/main/java/org/bukkit/command/ConsoleCommandSender.java @@ -0,0 +1,6 @@ +package org.bukkit.command; + +import org.bukkit.conversations.Conversable; + +public interface ConsoleCommandSender extends CommandSender, Conversable { +} diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java new file mode 100644 index 00000000..50257883 --- /dev/null +++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java @@ -0,0 +1,119 @@ +package org.bukkit.command; + +import java.util.ArrayList; + +import org.bukkit.Bukkit; + +public class FormattedCommandAlias extends Command { + private final String[] formatStrings; + + public FormattedCommandAlias(String alias, String[] formatStrings) { + super(alias); + this.formatStrings = formatStrings; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + boolean result = false; + ArrayList commands = new ArrayList(); + for (String formatString : formatStrings) { + try { + commands.add(buildCommand(formatString, args)); + } catch (Throwable throwable) { + if (throwable instanceof IllegalArgumentException) { + sender.sendMessage(throwable.getMessage()); + } else { + sender.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); + } + return false; + } + } + + for (String command : commands) { + result |= Bukkit.dispatchCommand(sender, command); + } + + return result; + } + + private String buildCommand(String formatString, String[] args) { + int index = formatString.indexOf('$'); + while (index != -1) { + int start = index; + + if (index > 0 && formatString.charAt(start - 1) == '\\') { + formatString = formatString.substring(0, start - 1) + formatString.substring(start); + index = formatString.indexOf('$', index); + continue; + } + + boolean required = false; + if (formatString.charAt(index + 1) == '$') { + required = true; + // Move index past the second $ + index++; + } + + // Move index past the $ + index++; + int argStart = index; + while (index < formatString.length() && inRange(((int) formatString.charAt(index)) - 48, 0, 9)) { + // Move index past current digit + index++; + } + + // No numbers found + if (argStart == index) { + throw new IllegalArgumentException("Invalid replacement token"); + } + + int position = Integer.parseInt(formatString.substring(argStart, index)); + + // Arguments are not 0 indexed + if (position == 0) { + throw new IllegalArgumentException("Invalid replacement token"); + } + + // Convert position to 0 index + position--; + + boolean rest = false; + if (index < formatString.length() && formatString.charAt(index) == '-') { + rest = true; + // Move index past the - + index++; + } + + int end = index; + + if (required && position >= args.length) { + throw new IllegalArgumentException("Missing required argument " + (position + 1)); + } + + StringBuilder replacement = new StringBuilder(); + if (rest && position < args.length) { + for (int i = position; i < args.length; i++) { + if (i != position) { + replacement.append(' '); + } + replacement.append(args[i]); + } + } else if (position < args.length) { + replacement.append(args[position]); + } + + formatString = formatString.substring(0, start) + replacement.toString() + formatString.substring(end); + // Move index past the replaced data so we don't process it again + index = start + replacement.length(); + + // Move to the next replacement token + index = formatString.indexOf('$', index); + } + + return formatString; + } + + private static boolean inRange(int i, int j, int k) { + return i >= j && i <= k; + } +} diff --git a/src/main/java/org/bukkit/command/MultipleCommandAlias.java b/src/main/java/org/bukkit/command/MultipleCommandAlias.java new file mode 100644 index 00000000..a0a41295 --- /dev/null +++ b/src/main/java/org/bukkit/command/MultipleCommandAlias.java @@ -0,0 +1,33 @@ +package org.bukkit.command; + +/** + * Represents a command that delegates to one or more other commands + */ +public class MultipleCommandAlias extends Command { + private Command[] commands; + + public MultipleCommandAlias(String name, Command[] commands) { + super(name); + this.commands = commands; + } + + /** + * Gets the commands associated with the multi-command alias. + * + * @return commands associated with alias + */ + public Command[] getCommands() { + return commands; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + boolean result = false; + + for (Command command : commands) { + result |= command.execute(sender, commandLabel, args); + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/command/PluginCommand.java b/src/main/java/org/bukkit/command/PluginCommand.java new file mode 100644 index 00000000..2ca2198a --- /dev/null +++ b/src/main/java/org/bukkit/command/PluginCommand.java @@ -0,0 +1,160 @@ +package org.bukkit.command; + +import java.util.List; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.Plugin; + +/** + * Represents a {@link Command} belonging to a plugin + */ +public final class PluginCommand extends Command implements PluginIdentifiableCommand { + private final Plugin owningPlugin; + private CommandExecutor executor; + private TabCompleter completer; + + protected PluginCommand(String name, Plugin owner) { + super(name); + this.executor = owner; + this.owningPlugin = owner; + this.usageMessage = ""; + } + + /** + * Executes the command, returning its success + * + * @param sender Source object which is executing this command + * @param commandLabel The alias of the command used + * @param args All arguments passed to the command, split via ' ' + * @return true if the command was successful, otherwise false + */ + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + boolean success = false; + + if (!owningPlugin.isEnabled()) { + throw new CommandException("Cannot execute command '" + commandLabel + "' in plugin " + owningPlugin.getDescription().getFullName() + " - plugin is disabled."); + } + + if (!testPermission(sender)) { + return true; + } + + try { + success = executor.onCommand(sender, this, commandLabel, args); + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing command '" + commandLabel + "' in plugin " + owningPlugin.getDescription().getFullName(), ex); + } + + if (!success && usageMessage.length() > 0) { + for (String line : usageMessage.replace("", commandLabel).split("\n")) { + sender.sendMessage(line); + } + } + + return success; + } + + /** + * Sets the {@link CommandExecutor} to run when parsing this command + * + * @param executor New executor to run + */ + public void setExecutor(CommandExecutor executor) { + this.executor = executor == null ? owningPlugin : executor; + } + + /** + * Gets the {@link CommandExecutor} associated with this command + * + * @return CommandExecutor object linked to this command + */ + public CommandExecutor getExecutor() { + return executor; + } + + /** + * Sets the {@link TabCompleter} to run when tab-completing this command. + *

+ * If no TabCompleter is specified, and the command's executor implements + * TabCompleter, then the executor will be used for tab completion. + * + * @param completer New tab completer + */ + public void setTabCompleter(TabCompleter completer) { + this.completer = completer; + } + + /** + * Gets the {@link TabCompleter} associated with this command. + * + * @return TabCompleter object linked to this command + */ + public TabCompleter getTabCompleter() { + return completer; + } + + /** + * Gets the owner of this PluginCommand + * + * @return Plugin that owns this command + */ + public Plugin getPlugin() { + return owningPlugin; + } + + /** + * {@inheritDoc} + *

+ * Delegates to the tab completer if present. + *

+ * If it is not present or returns null, will delegate to the current + * command executor if it implements {@link TabCompleter}. If a non-null + * list has not been found, will default to standard player name + * completion in {@link + * Command#tabComplete(CommandSender, String, String[])}. + *

+ * This method does not consider permissions. + * + * @throws CommandException if the completer or executor throw an + * exception during the process of tab-completing. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws CommandException, IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + List completions = null; + try { + if (completer != null) { + completions = completer.onTabComplete(sender, this, alias, args); + } + if (completions == null && executor instanceof TabCompleter) { + completions = ((TabCompleter) executor).onTabComplete(sender, this, alias, args); + } + } catch (Throwable ex) { + StringBuilder message = new StringBuilder(); + message.append("Unhandled exception during tab completion for command '/").append(alias).append(' '); + for (String arg : args) { + message.append(arg).append(' '); + } + message.deleteCharAt(message.length() - 1).append("' in plugin ").append(owningPlugin.getDescription().getFullName()); + throw new CommandException(message.toString(), ex); + } + + if (completions == null) { + return super.tabComplete(sender, alias, args); + } + return completions; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(super.toString()); + stringBuilder.deleteCharAt(stringBuilder.length() - 1); + stringBuilder.append(", ").append(owningPlugin.getDescription().getFullName()).append(')'); + return stringBuilder.toString(); + } +} diff --git a/src/main/java/org/bukkit/command/PluginCommandYamlParser.java b/src/main/java/org/bukkit/command/PluginCommandYamlParser.java new file mode 100644 index 00000000..5854583e --- /dev/null +++ b/src/main/java/org/bukkit/command/PluginCommandYamlParser.java @@ -0,0 +1,76 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +public class PluginCommandYamlParser { + + public static List parse(Plugin plugin) { + List pluginCmds = new ArrayList(); + + Map> map = plugin.getDescription().getCommands(); + + if (map == null) { + return pluginCmds; + } + + for (Entry> entry : map.entrySet()) { + if (entry.getKey().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load command " + entry.getKey() + " for plugin " + plugin.getName() + ": Illegal Characters"); + continue; + } + Command newCmd = new PluginCommand(entry.getKey(), plugin); + Object description = entry.getValue().get("description"); + Object usage = entry.getValue().get("usage"); + Object aliases = entry.getValue().get("aliases"); + Object permission = entry.getValue().get("permission"); + Object permissionMessage = entry.getValue().get("permission-message"); + + if (description != null) { + newCmd.setDescription(description.toString()); + } + + if (usage != null) { + newCmd.setUsage(usage.toString()); + } + + if (aliases != null) { + List aliasList = new ArrayList(); + + if (aliases instanceof List) { + for (Object o : (List) aliases) { + if (o.toString().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load alias " + o.toString() + " for plugin " + plugin.getName() + ": Illegal Characters"); + continue; + } + aliasList.add(o.toString()); + } + } else { + if (aliases.toString().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load alias " + aliases.toString() + " for plugin " + plugin.getName() + ": Illegal Characters"); + } else { + aliasList.add(aliases.toString()); + } + } + + newCmd.setAliases(aliasList); + } + + if (permission != null) { + newCmd.setPermission(permission.toString()); + } + + if (permissionMessage != null) { + newCmd.setPermissionMessage(permissionMessage.toString()); + } + + pluginCmds.add(newCmd); + } + return pluginCmds; + } +} diff --git a/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java b/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java new file mode 100644 index 00000000..c5e0d2c4 --- /dev/null +++ b/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java @@ -0,0 +1,19 @@ +package org.bukkit.command; + +import org.bukkit.plugin.Plugin; + +/** + * This interface is used by the help system to group commands into + * sub-indexes based on the {@link Plugin} they are a part of. Custom command + * implementations will need to implement this interface to have a sub-index + * automatically generated on the plugin's behalf. + */ +public interface PluginIdentifiableCommand { + + /** + * Gets the owner of this PluginIdentifiableCommand. + * + * @return Plugin that owns this PluginIdentifiableCommand. + */ + public Plugin getPlugin(); +} diff --git a/src/main/java/org/bukkit/command/ProxiedCommandSender.java b/src/main/java/org/bukkit/command/ProxiedCommandSender.java new file mode 100644 index 00000000..24c4ebad --- /dev/null +++ b/src/main/java/org/bukkit/command/ProxiedCommandSender.java @@ -0,0 +1,20 @@ + +package org.bukkit.command; + +public interface ProxiedCommandSender extends CommandSender { + + /** + * Returns the CommandSender which triggered this proxied command + * + * @return the caller which triggered the command + */ + CommandSender getCaller(); + + /** + * Returns the CommandSender which is being used to call the command + * + * @return the caller which the command is being run as + */ + CommandSender getCallee(); + +} diff --git a/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java b/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java new file mode 100644 index 00000000..dc3bc1d1 --- /dev/null +++ b/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java @@ -0,0 +1,4 @@ +package org.bukkit.command; + +public interface RemoteConsoleCommandSender extends CommandSender { +} diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java new file mode 100644 index 00000000..afdea280 --- /dev/null +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -0,0 +1,268 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.command.defaults.*; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; + +public class SimpleCommandMap implements CommandMap { + private static final Pattern PATTERN_ON_SPACE = Pattern.compile(" ", Pattern.LITERAL); + protected final Map knownCommands = new HashMap(); + private final Server server; + + public SimpleCommandMap(final Server server) { + this.server = server; + setDefaultCommands(); + } + + private void setDefaultCommands() { + register("bukkit", new PluginsCommand("plugins")); + register("bukkit", new TimingsCommand("timings")); + } + + public void setFallbackCommands() { + register("bukkit", new HelpCommand()); + } + + /** + * {@inheritDoc} + */ + public void registerAll(String fallbackPrefix, List commands) { + if (commands != null) { + for (Command c : commands) { + register(fallbackPrefix, c); + } + } + } + + /** + * {@inheritDoc} + */ + public boolean register(String fallbackPrefix, Command command) { + return register(command.getName(), fallbackPrefix, command); + } + + /** + * {@inheritDoc} + */ + public boolean register(String label, String fallbackPrefix, Command command) { + label = label.toLowerCase(java.util.Locale.ENGLISH).trim(); + fallbackPrefix = fallbackPrefix.toLowerCase(java.util.Locale.ENGLISH).trim(); + boolean registered = register(label, command, false, fallbackPrefix); + + Iterator iterator = command.getAliases().iterator(); + while (iterator.hasNext()) { + if (!register(iterator.next(), command, true, fallbackPrefix)) { + iterator.remove(); + } + } + + // If we failed to register under the real name, we need to set the command label to the direct address + if (!registered) { + command.setLabel(fallbackPrefix + ":" + label); + } + + // Register to us so further updates of the commands label and aliases are postponed until its reregistered + command.register(this); + + return registered; + } + + /** + * Registers a command with the given name is possible. Also uses + * fallbackPrefix to create a unique name. + * + * @param label the name of the command, without the '/'-prefix. + * @param command the command to register + * @param isAlias whether the command is an alias + * @param fallbackPrefix a prefix which is prepended to the command for a + * unique address + * @return true if command was registered, false otherwise. + */ + private synchronized boolean register(String label, Command command, boolean isAlias, String fallbackPrefix) { + knownCommands.put(fallbackPrefix + ":" + label, command); + if ((command instanceof BukkitCommand || isAlias) && knownCommands.containsKey(label)) { + // Request is for an alias/fallback command and it conflicts with + // a existing command or previous alias ignore it + // Note: This will mean it gets removed from the commands list of active aliases + return false; + } + + boolean registered = true; + + // If the command exists but is an alias we overwrite it, otherwise we return + Command conflict = knownCommands.get(label); + if (conflict != null && conflict.getLabel().equals(label)) { + return false; + } + + if (!isAlias) { + command.setLabel(label); + } + knownCommands.put(label, command); + + return registered; + } + + /** + * {@inheritDoc} + */ + public boolean dispatch(CommandSender sender, String commandLine) throws CommandException { + String[] args = PATTERN_ON_SPACE.split(commandLine); + + if (args.length == 0) { + return false; + } + + String sentCommandLabel = args[0].toLowerCase(java.util.Locale.ENGLISH); + Command target = getCommand(sentCommandLabel); + + if (target == null) { + return false; + } + + try { + target.timings.startTiming(); // Spigot + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) + target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length)); + target.timings.stopTiming(); // Spigot + } catch (CommandException ex) { + target.timings.stopTiming(); // Spigot + throw ex; + } catch (Throwable ex) { + target.timings.stopTiming(); // Spigot + throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex); + } + + // return true as command was handled + return true; + } + + public synchronized void clearCommands() { + for (Map.Entry entry : knownCommands.entrySet()) { + entry.getValue().unregister(this); + } + knownCommands.clear(); + setDefaultCommands(); + } + + public Command getCommand(String name) { + Command target = knownCommands.get(name.toLowerCase(java.util.Locale.ENGLISH)); + return target; + } + + public List tabComplete(CommandSender sender, String cmdLine) { + return tabComplete(sender, cmdLine, null); + } + + public List tabComplete(CommandSender sender, String cmdLine, Location location) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(cmdLine, "Command line cannot null"); + + int spaceIndex = cmdLine.indexOf(' '); + + if (spaceIndex == -1) { + ArrayList completions = new ArrayList(); + Map knownCommands = this.knownCommands; + + final String prefix = (sender instanceof Player ? "/" : ""); + + for (Map.Entry commandEntry : knownCommands.entrySet()) { + Command command = commandEntry.getValue(); + + if (!command.testPermissionSilent(sender)) { + continue; + } + + String name = commandEntry.getKey(); // Use the alias, not command name + + if (StringUtil.startsWithIgnoreCase(name, cmdLine)) { + completions.add(prefix + name); + } + } + + Collections.sort(completions, String.CASE_INSENSITIVE_ORDER); + return completions; + } + + String commandName = cmdLine.substring(0, spaceIndex); + Command target = getCommand(commandName); + + if (target == null) { + return null; + } + + if (!target.testPermissionSilent(sender)) { + return null; + } + + String argLine = cmdLine.substring(spaceIndex + 1, cmdLine.length()); + String[] args = PATTERN_ON_SPACE.split(argLine, -1); + + try { + return target.tabComplete(sender, commandName, args, location); + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target, ex); + } + } + + public Collection getCommands() { + return Collections.unmodifiableCollection(knownCommands.values()); + } + + public void registerServerAliases() { + Map values = server.getCommandAliases(); + + for (Map.Entry entry : values.entrySet()) { + String alias = entry.getKey(); + if (alias.contains(" ")) { + server.getLogger().warning("Could not register alias " + alias + " because it contains illegal characters"); + continue; + } + + String[] commandStrings = entry.getValue(); + List targets = new ArrayList(); + StringBuilder bad = new StringBuilder(); + + for (String commandString : commandStrings) { + String[] commandArgs = commandString.split(" "); + Command command = getCommand(commandArgs[0]); + + if (command == null) { + if (bad.length() > 0) { + bad.append(", "); + } + bad.append(commandString); + } else { + targets.add(commandString); + } + } + + if (bad.length() > 0) { + server.getLogger().warning("Could not register alias " + alias + " because it contains commands that do not exist: " + bad); + continue; + } + + // We register these as commands so they have absolute priority. + if (targets.size() > 0) { + knownCommands.put(alias.toLowerCase(java.util.Locale.ENGLISH), new FormattedCommandAlias(alias.toLowerCase(java.util.Locale.ENGLISH), targets.toArray(new String[targets.size()]))); + } else { + knownCommands.remove(alias.toLowerCase(java.util.Locale.ENGLISH)); + } + } + } +} diff --git a/src/main/java/org/bukkit/command/TabCompleter.java b/src/main/java/org/bukkit/command/TabCompleter.java new file mode 100644 index 00000000..3752ba9f --- /dev/null +++ b/src/main/java/org/bukkit/command/TabCompleter.java @@ -0,0 +1,24 @@ +package org.bukkit.command; + +import java.util.List; + +/** + * Represents a class which can suggest tab completions for commands. + */ +public interface TabCompleter { + + /** + * Requests a list of possible completions for a command argument. + * + * @param sender Source of the command. For players tab-completing a + * command inside of a command block, this will be the player, not + * the command block. + * @param command Command which was executed + * @param alias The alias used + * @param args The arguments passed to the command, including final + * partial argument to be completed and command label + * @return A List of possible completions for the final argument, or null + * to default to the command executor + */ + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args); +} diff --git a/src/main/java/org/bukkit/command/TabExecutor.java b/src/main/java/org/bukkit/command/TabExecutor.java new file mode 100644 index 00000000..6b8e3fb2 --- /dev/null +++ b/src/main/java/org/bukkit/command/TabExecutor.java @@ -0,0 +1,8 @@ +package org.bukkit.command; + +/** + * This class is provided as a convenience to implement both TabCompleter and + * CommandExecutor. + */ +public interface TabExecutor extends TabCompleter, CommandExecutor { +} diff --git a/src/main/java/org/bukkit/command/defaults/BukkitCommand.java b/src/main/java/org/bukkit/command/defaults/BukkitCommand.java new file mode 100644 index 00000000..23c85800 --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/BukkitCommand.java @@ -0,0 +1,15 @@ +package org.bukkit.command.defaults; + +import java.util.List; + +import org.bukkit.command.Command; + +public abstract class BukkitCommand extends Command { + protected BukkitCommand(String name) { + super(name); + } + + protected BukkitCommand(String name, String description, String usageMessage, List aliases) { + super(name, description, usageMessage, aliases); + } +} diff --git a/src/main/java/org/bukkit/command/defaults/HelpCommand.java b/src/main/java/org/bukkit/command/defaults/HelpCommand.java new file mode 100644 index 00000000..ff0867ed --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/HelpCommand.java @@ -0,0 +1,228 @@ +package org.bukkit.command.defaults; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.math.NumberUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.util.ChatPaginator; + +import com.google.common.collect.ImmutableList; + +public class HelpCommand extends BukkitCommand { + public HelpCommand() { + super("help"); + this.description = "Shows the help menu"; + this.usageMessage = "/help \n/help \n/help "; + this.setPermission("bukkit.command.help"); + this.setAliases(Arrays.asList("?")); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; + + String command; + int pageNumber; + int pageHeight; + int pageWidth; + + if (args.length == 0) { + command = ""; + pageNumber = 1; + } else if (NumberUtils.isDigits(args[args.length - 1])) { + command = StringUtils.join(ArrayUtils.subarray(args, 0, args.length - 1), " "); + try { + pageNumber = NumberUtils.createInteger(args[args.length - 1]); + } catch (NumberFormatException exception) { + pageNumber = 1; + } + if (pageNumber <= 0) { + pageNumber = 1; + } + } else { + command = StringUtils.join(args, " "); + pageNumber = 1; + } + + if (sender instanceof ConsoleCommandSender) { + pageHeight = ChatPaginator.UNBOUNDED_PAGE_HEIGHT; + pageWidth = ChatPaginator.UNBOUNDED_PAGE_WIDTH; + } else { + pageHeight = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 1; + pageWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; + } + + HelpMap helpMap = Bukkit.getServer().getHelpMap(); + HelpTopic topic = helpMap.getHelpTopic(command); + + if (topic == null) { + topic = helpMap.getHelpTopic("/" + command); + } + + if (topic == null) { + topic = findPossibleMatches(command); + } + + if (topic == null || !topic.canSee(sender)) { + sender.sendMessage(ChatColor.RED + "No help for " + command); + return true; + } + + ChatPaginator.ChatPage page = ChatPaginator.paginate(topic.getFullText(sender), pageNumber, pageWidth, pageHeight); + + StringBuilder header = new StringBuilder(); + header.append(ChatColor.YELLOW); + header.append("--------- "); + header.append(ChatColor.WHITE); + header.append("Help: "); + header.append(topic.getName()); + header.append(" "); + if (page.getTotalPages() > 1) { + header.append("("); + header.append(page.getPageNumber()); + header.append("/"); + header.append(page.getTotalPages()); + header.append(") "); + } + header.append(ChatColor.YELLOW); + for (int i = header.length(); i < ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; i++) { + header.append("-"); + } + sender.sendMessage(header.toString()); + + sender.sendMessage(page.getLines()); + + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + List matchedTopics = new ArrayList(); + String searchString = args[0]; + for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { + String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); + + if (trimmedTopic.startsWith(searchString)) { + matchedTopics.add(trimmedTopic); + } + } + return matchedTopics; + } + return ImmutableList.of(); + } + + protected HelpTopic findPossibleMatches(String searchString) { + int maxDistance = (searchString.length() / 5) + 3; + Set possibleMatches = new TreeSet(HelpTopicComparator.helpTopicComparatorInstance()); + + if (searchString.startsWith("/")) { + searchString = searchString.substring(1); + } + + for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { + String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); + + if (trimmedTopic.length() < searchString.length()) { + continue; + } + + if (Character.toLowerCase(trimmedTopic.charAt(0)) != Character.toLowerCase(searchString.charAt(0))) { + continue; + } + + if (damerauLevenshteinDistance(searchString, trimmedTopic.substring(0, searchString.length())) < maxDistance) { + possibleMatches.add(topic); + } + } + + if (possibleMatches.size() > 0) { + return new IndexHelpTopic("Search", null, null, possibleMatches, "Search for: " + searchString); + } else { + return null; + } + } + + /** + * Computes the Dameraur-Levenshtein Distance between two strings. Adapted + * from the algorithm at Wikipedia: Damerau–Levenshtein distance + * + * @param s1 The first string being compared. + * @param s2 The second string being compared. + * @return The number of substitutions, deletions, insertions, and + * transpositions required to get from s1 to s2. + */ + protected static int damerauLevenshteinDistance(String s1, String s2) { + if (s1 == null && s2 == null) { + return 0; + } + if (s1 != null && s2 == null) { + return s1.length(); + } + if (s1 == null && s2 != null) { + return s2.length(); + } + + int s1Len = s1.length(); + int s2Len = s2.length(); + int[][] H = new int[s1Len + 2][s2Len + 2]; + + int INF = s1Len + s2Len; + H[0][0] = INF; + for (int i = 0; i <= s1Len; i++) { + H[i + 1][1] = i; + H[i + 1][0] = INF; + } + for (int j = 0; j <= s2Len; j++) { + H[1][j + 1] = j; + H[0][j + 1] = INF; + } + + Map sd = new HashMap(); + for (char Letter : (s1 + s2).toCharArray()) { + if (!sd.containsKey(Letter)) { + sd.put(Letter, 0); + } + } + + for (int i = 1; i <= s1Len; i++) { + int DB = 0; + for (int j = 1; j <= s2Len; j++) { + int i1 = sd.get(s2.charAt(j - 1)); + int j1 = DB; + + if (s1.charAt(i - 1) == s2.charAt(j - 1)) { + H[i + 1][j + 1] = H[i][j]; + DB = j; + } else { + H[i + 1][j + 1] = Math.min(H[i][j], Math.min(H[i + 1][j], H[i][j + 1])) + 1; + } + + H[i + 1][j + 1] = Math.min(H[i + 1][j + 1], H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1)); + } + sd.put(s1.charAt(i - 1), i); + } + + return H[s1Len + 1][s2Len + 1]; + } +} diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java new file mode 100644 index 00000000..2effc43f --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java @@ -0,0 +1,93 @@ +package org.bukkit.command.defaults; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +public class PluginsCommand extends BukkitCommand { + public PluginsCommand(String name) { + super(name); + this.description = "Gets a list of plugins running on the server"; + this.usageMessage = "/plugins [load|unload|reload] [name]"; + this.setPermission("bukkit.command.plugins"); + this.setAliases(Arrays.asList("pl")); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) { + return true; + } + + if (args.length == 0) { + sender.sendMessage("Plugins " + getPluginList()); + return false; + } + + switch (args[0].toLowerCase(Locale.ENGLISH)) { +// case "load": +// PluginManagers.loadPluginCommand(sender, currentAlias, args); +// break; +// case "unload": +// PluginManagers.unloadPluginCommand(sender, currentAlias, args); +// break; +// case "reload": +// PluginManagers.reloadPluginCommand(sender, currentAlias, args); +// break; + default: + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } +// return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + List tabs = new ArrayList(); + if (args.length > 1) { + String action = args[0].toLowerCase(); + if (action.equals("unload")) { + for (Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { + tabs.add(plugin.getName()); + } + } + else if (action.equals("reload")) { + for (Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { + tabs.add(plugin.getName()); + } + } + } + return tabs; + } + + private String getPluginList() { + // Paper start + TreeMap plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + plugins.put(plugin.getDescription().getName(), plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED); + } + + StringBuilder pluginList = new StringBuilder(); + for (Map.Entry entry : plugins.entrySet()) { + if (pluginList.length() > 0) { + pluginList.append(ChatColor.WHITE); + pluginList.append(", "); + } + + pluginList.append(entry.getValue()); + pluginList.append(entry.getKey()); + } + + return "(" + plugins.size() + "): " + pluginList.toString(); + // Paper end + } +} diff --git a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java new file mode 100644 index 00000000..0ab7e295 --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java @@ -0,0 +1,37 @@ +package org.bukkit.command.defaults; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +public class ReloadCommand extends BukkitCommand { + public ReloadCommand(String name) { + super(name); + this.description = "Reloads the server configuration and plugins"; + this.usageMessage = "/reload"; + this.setPermission("bukkit.command.reload"); + this.setAliases(Arrays.asList("rl")); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; + + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues when using some plugins."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + Bukkit.reload(); + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete."); + + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/bukkit/command/defaults/TimingsCommand.java b/src/main/java/org/bukkit/command/defaults/TimingsCommand.java new file mode 100644 index 00000000..a2dabe82 --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/TimingsCommand.java @@ -0,0 +1,252 @@ +package org.bukkit.command.defaults; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; +import org.bukkit.plugin.TimedRegisteredListener; +import org.bukkit.util.StringUtil; + +import com.google.common.collect.ImmutableList; + +// Spigot start +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.logging.Level; + +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.plugin.SimplePluginManager; +import org.spigotmc.CustomTimingsHandler; +// Spigot end + +public class TimingsCommand extends BukkitCommand { + private static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste"); // Spigot + public static long timingStart = 0; // Spigot + + public TimingsCommand(String name) { + super(name); + this.description = "Manages Spigot Timings data to see performance of the server."; // Spigot + this.usageMessage = "/timings "; // Spigot + this.setPermission("bukkit.command.timings"); + } + + // Spigot start - redesigned Timings Command + public void executeSpigotTimings(CommandSender sender, String[] args) { + if ( "on".equals( args[0] ) ) + { + ( (SimplePluginManager) Bukkit.getPluginManager() ).useTimings( true ); + CustomTimingsHandler.reload(); + sender.sendMessage( "Enabled Timings & Reset" ); + return; + } else if ( "off".equals( args[0] ) ) + { + ( (SimplePluginManager) Bukkit.getPluginManager() ).useTimings( false ); + sender.sendMessage( "Disabled Timings" ); + return; + } + + if ( !Bukkit.getPluginManager().useTimings() ) + { + sender.sendMessage( "Please enable timings by typing /timings on" ); + return; + } + + boolean paste = "paste".equals( args[0] ); + if ("reset".equals(args[0])) { + CustomTimingsHandler.reload(); + sender.sendMessage("Timings reset"); + } else if ("merged".equals(args[0]) || "report".equals(args[0]) || paste) { + long sampleTime = System.nanoTime() - timingStart; + int index = 0; + File timingFolder = new File("timings"); + timingFolder.mkdirs(); + File timings = new File(timingFolder, "timings.txt"); + ByteArrayOutputStream bout = ( paste ) ? new ByteArrayOutputStream() : null; + while (timings.exists()) timings = new File(timingFolder, "timings" + (++index) + ".txt"); + PrintStream fileTimings = null; + try { + fileTimings = ( paste ) ? new PrintStream( bout ) : new PrintStream( timings ); + + CustomTimingsHandler.printTimings(fileTimings); + fileTimings.println( "Sample time " + sampleTime + " (" + sampleTime / 1E9 + "s)" ); + + fileTimings.println( "" ); + fileTimings.println( Bukkit.spigot().getConfig().saveToString() ); + fileTimings.println( "" ); + + if ( paste ) + { + new PasteThread( sender, bout ).start(); + return; + } + + sender.sendMessage("Timings written to " + timings.getPath()); + sender.sendMessage( "Paste contents of file into form at http://www.spigotmc.org/go/timings to read results." ); + + } catch (IOException e) { + } finally { + if (fileTimings != null) { + fileTimings.close(); + } + } + } + } + // Spigot end + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; + if (args.length < 1) { // Spigot + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + if (true) { executeSpigotTimings(sender, args); return true; } // Spigot + if (!sender.getServer().getPluginManager().useTimings()) { + sender.sendMessage("Please enable timings by setting \"settings.plugin-profiling\" to true in bukkit.yml"); + return true; + } + + boolean separate = "separate".equalsIgnoreCase(args[0]); + if ("reset".equalsIgnoreCase(args[0])) { + for (HandlerList handlerList : HandlerList.getHandlerLists()) { + for (RegisteredListener listener : handlerList.getRegisteredListeners()) { + if (listener instanceof TimedRegisteredListener) { + ((TimedRegisteredListener) listener).reset(); + } + } + } + sender.sendMessage("Timings reset"); + } else if ("merged".equalsIgnoreCase(args[0]) || separate) { + + int index = 0; + int pluginIdx = 0; + File timingFolder = new File("timings"); + timingFolder.mkdirs(); + File timings = new File(timingFolder, "timings.txt"); + File names = null; + while (timings.exists()) timings = new File(timingFolder, "timings" + (++index) + ".txt"); + PrintStream fileTimings = null; + PrintStream fileNames = null; + try { + fileTimings = new PrintStream(timings); + if (separate) { + names = new File(timingFolder, "names" + index + ".txt"); + fileNames = new PrintStream(names); + } + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + pluginIdx++; + long totalTime = 0; + if (separate) { + fileNames.println(pluginIdx + " " + plugin.getDescription().getFullName()); + fileTimings.println("Plugin " + pluginIdx); + } + else fileTimings.println(plugin.getDescription().getFullName()); + for (RegisteredListener listener : HandlerList.getRegisteredListeners(plugin)) { + if (listener instanceof TimedRegisteredListener) { + TimedRegisteredListener trl = (TimedRegisteredListener) listener; + long time = trl.getTotalTime(); + int count = trl.getCount(); + if (count == 0) continue; + long avg = time / count; + totalTime += time; + Class eventClass = trl.getEventClass(); + if (count > 0 && eventClass != null) { + fileTimings.println(" " + eventClass.getSimpleName() + (trl.hasMultiple() ? " (and sub-classes)" : "") + " Time: " + time + " Count: " + count + " Avg: " + avg); + } + } + } + fileTimings.println(" Total time " + totalTime + " (" + totalTime / 1000000000 + "s)"); + } + sender.sendMessage("Timings written to " + timings.getPath()); + if (separate) sender.sendMessage("Names written to " + names.getPath()); + } catch (IOException e) { + } finally { + if (fileTimings != null) { + fileTimings.close(); + } + if (fileNames != null) { + fileNames.close(); + } + } + } else { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, new ArrayList(TIMINGS_SUBCOMMANDS.size())); + } + return ImmutableList.of(); + } + + // Spigot start + private static class PasteThread extends Thread + { + + private final CommandSender sender; + private final ByteArrayOutputStream bout; + + public PasteThread(CommandSender sender, ByteArrayOutputStream bout) + { + super( "Timings paste thread" ); + this.sender = sender; + this.bout = bout; + } + + @Override + public synchronized void start() { + if (sender instanceof RemoteConsoleCommandSender) { + run(); + } else { + super.start(); + } + } + + @Override + public void run() + { + try + { + HttpURLConnection con = (HttpURLConnection) new URL( "https://timings.spigotmc.org/paste" ).openConnection(); + con.setDoOutput( true ); + con.setRequestMethod( "POST" ); + con.setInstanceFollowRedirects( false ); + + OutputStream out = con.getOutputStream(); + out.write( bout.toByteArray() ); + out.close(); + + com.google.gson.JsonObject location = new com.google.gson.Gson().fromJson(new java.io.InputStreamReader(con.getInputStream()), com.google.gson.JsonObject.class); + con.getInputStream().close(); + + String pasteID = location.get( "key" ).getAsString(); + sender.sendMessage( ChatColor.GREEN + "Timings results can be viewed at https://www.spigotmc.org/go/timings?url=" + pasteID ); + } catch ( IOException ex ) + { + sender.sendMessage( ChatColor.RED + "Error pasting timings, check your console for more information" ); + Bukkit.getServer().getLogger().log( Level.WARNING, "Could not paste timings", ex ); + } + } + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java new file mode 100644 index 00000000..a78b4afc --- /dev/null +++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java @@ -0,0 +1,315 @@ +package org.bukkit.command.defaults; + +import com.google.common.base.Charsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraftforge.common.ForgeVersion; +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.util.StringUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Resources; +import java.io.BufferedReader; +import java.io.IOException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +// LB Start +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +// LB End + +public class VersionCommand extends BukkitCommand { + public VersionCommand(String name) { + super(name); + + this.description = "Gets the version of this server including any plugins in use"; + this.usageMessage = "/version [plugin name]"; + this.setPermission("bukkit.command.version"); + this.setAliases(Arrays.asList("ver", "about")); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; + + if (args.length == 0) { + sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " with Forge version " + ForgeVersion.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); + sendVersion(sender); + } else { + StringBuilder name = new StringBuilder(); + + for (String arg : args) { + if (name.length() > 0) { + name.append(' '); + } + + name.append(arg); + } + + String pluginName = name.toString(); + Plugin exactPlugin = Bukkit.getPluginManager().getPlugin(pluginName); + if (exactPlugin != null) { + describeToSender(exactPlugin, sender); + return true; + } + + boolean found = false; + pluginName = pluginName.toLowerCase(java.util.Locale.ENGLISH); + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + if (plugin.getName().toLowerCase(java.util.Locale.ENGLISH).contains(pluginName)) { + describeToSender(plugin, sender); + found = true; + } + } + + if (!found) { + sender.sendMessage("This server is not running any plugin by that name."); + sender.sendMessage("Use /plugins to get a list of plugins."); + } + } + return true; + } + + private void describeToSender(Plugin plugin, CommandSender sender) { + PluginDescriptionFile desc = plugin.getDescription(); + sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " version " + ChatColor.GREEN + desc.getVersion()); + + if (desc.getDescription() != null) { + sender.sendMessage(desc.getDescription()); + } + + if (desc.getWebsite() != null) { + sender.sendMessage("Website: " + ChatColor.GREEN + desc.getWebsite()); + } + + if (!desc.getAuthors().isEmpty()) { + if (desc.getAuthors().size() == 1) { + sender.sendMessage("Author: " + getAuthors(desc)); + } else { + sender.sendMessage("Authors: " + getAuthors(desc)); + } + } + } + + private String getAuthors(final PluginDescriptionFile desc) { + StringBuilder result = new StringBuilder(); + List authors = desc.getAuthors(); + + for (int i = 0; i < authors.size(); i++) { + if (result.length() > 0) { + result.append(ChatColor.WHITE); + + if (i < authors.size() - 1) { + result.append(", "); + } else { + result.append(" and "); + } + } + + result.append(ChatColor.GREEN); + result.append(authors.get(i)); + } + + return result.toString(); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + List completions = new ArrayList(); + String toComplete = args[0].toLowerCase(java.util.Locale.ENGLISH); + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + if (StringUtil.startsWithIgnoreCase(plugin.getName(), toComplete)) { + completions.add(plugin.getName()); + } + } + return completions; + } + return ImmutableList.of(); + } + + private final ReentrantLock versionLock = new ReentrantLock(); + private boolean hasVersion = false; + private String versionMessage = null; + private final Set versionWaiters = new HashSet(); + private boolean versionTaskStarted = false; + private long lastCheck = 0; + + private void sendVersion(CommandSender sender) { + if (hasVersion) { + if (System.currentTimeMillis() - lastCheck > 21600000) { + lastCheck = System.currentTimeMillis(); + hasVersion = false; + } else { + sender.sendMessage(versionMessage); + return; + } + } + versionLock.lock(); + try { + if (hasVersion) { + sender.sendMessage(versionMessage); + return; + } + versionWaiters.add(sender); + sender.sendMessage("Checking version, please wait..."); + if (!versionTaskStarted) { + versionTaskStarted = true; + new Thread(() -> obtainVersion()).start(); + } + } finally { + versionLock.unlock(); + } + } + + private void obtainVersion() { + String version = Bukkit.getVersion(); + if (version == null) version = "Custom"; + if (version.startsWith("git-LavaBukkit-")) { + String[] parts = version.substring("git-LavaBukkit-".length()).split("[-\\s]"); + int distance = getDistance(null, parts[0]); + switch (distance) { + case -1: + setVersionMessage("Error obtaining version information"); + break; + case 0: + setVersionMessage("You are running the latest version"); + break; + case -2: + setVersionMessage("Unknown version"); + break; + default: + setVersionMessage("You are " + distance + " version(s) behind"); + } + } else if (version.startsWith("git-Bukkit-")) { + version = version.substring("git-Bukkit-".length()); + int cbVersions = getDistance("craftbukkit", version.substring(0, version.indexOf(' '))); + if (cbVersions == -1) { + setVersionMessage("Error obtaining version information"); + } else { + if (cbVersions == 0) { + setVersionMessage("You are running the latest version"); + } else { + setVersionMessage("You are " + cbVersions + " version(s) behind"); + } + } + } else { + setVersionMessage("Unknown version, custom build?"); + } + } + + private void setVersionMessage(String msg) { + lastCheck = System.currentTimeMillis(); + versionMessage = msg; + versionLock.lock(); + try { + hasVersion = true; + versionTaskStarted = false; + for (CommandSender sender : versionWaiters) { + sender.sendMessage(versionMessage); + } + versionWaiters.clear(); + } finally { + versionLock.unlock(); + } + } + + // LB Start - Taken from Paper / Modified by GMatrixGames + private static int getDistance(String repo, String verInfo) { + /* try { + int currentVer = Integer.decode(verInfo); + return getFromJenkins(currentVer); + } catch (NumberFormatException ex) { + verInfo = verInfo.replace("\"", ""); + return getFromRepo("MatrixDevTeam/LavaBukkit", verInfo); + } */ + + verInfo = verInfo.replace("\"", ""); + return getFromRepo("MatrixDevTeam/LavaBukkit", verInfo); + /* + BufferedReader reader = Resources.asCharSource( + new URL("https://hub.spigotmc.org/stash/rest/api/1.0/projects/SPIGOT/repos/" + repo + "/commits?since=" + URLEncoder.encode(hash, "UTF-8") + "&withCounts=true"), + Charsets.UTF_8 + ).openBufferedStream(); + try { + JSONObject obj = (JSONObject) new JSONParser().parse(reader); + return ((Number) obj.get("totalCount")).intValue(); + } catch (ParseException ex) { + ex.printStackTrace(); + return -1; + } finally { + reader.close(); + } + */ + } + + private static int getFromJenkins(int currentVer) { + try { + BufferedReader reader = Resources.asCharSource( + new URL("https://ci.destroystokyo.com/job/Paper/lastSuccessfulBuild/buildNumber"), // Paper + Charsets.UTF_8 + ).openBufferedStream(); + try { + int newVer = Integer.decode(reader.readLine()); + return newVer - currentVer; + } catch (NumberFormatException ex) { + ex.printStackTrace(); + return -2; + } finally { + reader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + return -1; + } + } + + // Contributed by Techcable in GH PR #65 of Paper / Modified by GMatrixGames + private static final String BRANCH = "master"; + private static int getFromRepo(String repo, String hash) { + try { + HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + BRANCH + "..." + hash).openConnection(); + connection.connect(); + if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit + try ( + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8)) + ) { + JSONObject obj = (JSONObject) new JSONParser().parse(reader); + String status = (String) obj.get("status"); + switch (status) { + case "identical": + return 0; + case "behind": + return ((Number) obj.get("behind_by")).intValue(); + default: + return -1; + } + } catch (ParseException | NumberFormatException e) { + e.printStackTrace(); + return -1; + } + } catch (IOException e) { + e.printStackTrace(); + return -1; + } + } + + // MB End +} diff --git a/src/main/java/org/bukkit/configuration/Configuration.java b/src/main/java/org/bukkit/configuration/Configuration.java new file mode 100644 index 00000000..0e3aa0bd --- /dev/null +++ b/src/main/java/org/bukkit/configuration/Configuration.java @@ -0,0 +1,84 @@ +package org.bukkit.configuration; + +import java.util.Map; + +/** + * Represents a source of configurable options and settings + */ +public interface Configuration extends ConfigurationSection { + /** + * Sets the default value of the given path as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * If value is null, the value will be removed from the default + * Configuration source. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(String path, Object value); + + /** + * Sets the default values of the given paths as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default values. + * + * @param defaults A map of Path{@literal ->}Values to add to defaults. + * @throws IllegalArgumentException Thrown if defaults is null. + */ + public void addDefaults(Map defaults); + + /** + * Sets the default values of the given paths as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * This method will not hold a reference to the specified Configuration, + * nor will it automatically update if that Configuration ever changes. If + * you require this, you should set the default source with {@link + * #setDefaults(Configuration)}. + * + * @param defaults A configuration holding a list of defaults to copy. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void addDefaults(Configuration defaults); + + /** + * Sets the source of all default values for this {@link Configuration}. + *

+ * If a previous source was set, or previous default values were defined, + * then they will not be copied to the new source. + * + * @param defaults New source of default values for this configuration. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void setDefaults(Configuration defaults); + + /** + * Gets the source {@link Configuration} for this configuration. + *

+ * If no configuration source was set, but default values were added, then + * a {@link MemoryConfiguration} will be returned. If no source was set + * and no defaults were set, then this method will return null. + * + * @return Configuration source for default values, or null if none exist. + */ + public Configuration getDefaults(); + + /** + * Gets the {@link ConfigurationOptions} for this {@link Configuration}. + *

+ * All setters through this method are chainable. + * + * @return Options for this configuration + */ + public ConfigurationOptions options(); +} diff --git a/src/main/java/org/bukkit/configuration/ConfigurationOptions.java b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java new file mode 100644 index 00000000..31a644ab --- /dev/null +++ b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java @@ -0,0 +1,90 @@ +package org.bukkit.configuration; + +/** + * Various settings for controlling the input and output of a {@link + * Configuration} + */ +public class ConfigurationOptions { + private char pathSeparator = '.'; + private boolean copyDefaults = false; + private final Configuration configuration; + + protected ConfigurationOptions(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Returns the {@link Configuration} that this object is responsible for. + * + * @return Parent configuration + */ + public Configuration configuration() { + return configuration; + } + + /** + * Gets the char that will be used to separate {@link + * ConfigurationSection}s + *

+ * This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @return Path separator + */ + public char pathSeparator() { + return pathSeparator; + } + + /** + * Sets the char that will be used to separate {@link + * ConfigurationSection}s + *

+ * This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @param value Path separator + * @return This object, for chaining + */ + public ConfigurationOptions pathSeparator(char value) { + this.pathSeparator = value; + return this; + } + + /** + * Checks if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + *

+ * If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(String)}. The default value is + * false. + * + * @return Whether or not defaults are directly copied + */ + public boolean copyDefaults() { + return copyDefaults; + } + + /** + * Sets if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + *

+ * If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(String)}. The default value is + * false. + * + * @param value Whether or not defaults are directly copied + * @return This object, for chaining + */ + public ConfigurationOptions copyDefaults(boolean value) { + this.copyDefaults = value; + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/ConfigurationSection.java b/src/main/java/org/bukkit/configuration/ConfigurationSection.java new file mode 100644 index 00000000..73258220 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/ConfigurationSection.java @@ -0,0 +1,861 @@ +package org.bukkit.configuration; + +import java.util.Map; +import java.util.Set; +import java.util.List; + +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.Vector; +import org.bukkit.inventory.ItemStack; + +/** + * Represents a section of a {@link Configuration} + */ +public interface ConfigurationSection { + /** + * Gets a set containing all keys in this section. + *

+ * If deep is set to true, then this will contain all the keys within any + * child {@link ConfigurationSection}s (and their children, etc). These + * will be in a valid path notation for you to use. + *

+ * If deep is set to false, then this will contain only the keys of any + * direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Set of keys contained within this ConfigurationSection. + */ + public Set getKeys(boolean deep); + + /** + * Gets a Map containing all keys and their values for this section. + *

+ * If deep is set to true, then this will contain all the keys and values + * within any child {@link ConfigurationSection}s (and their children, + * etc). These keys will be in a valid path notation for you to use. + *

+ * If deep is set to false, then this will contain only the keys and + * values of any direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Map of keys and values of this section. + */ + public Map getValues(boolean deep); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + *

+ * If the value for the requested path does not exist but a default value + * has been specified, this will return true. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, either via + * default or being set. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean contains(String path); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + *

+ * If the value for the requested path does not exist, the boolean parameter + * of true has been specified, a default value for the path exists, this + * will return true. + *

+ * If a boolean parameter of false has been specified, true will only be + * returned if there is a set value for the specified path. + * + * @param path Path to check for existence. + * @param ignoreDefault Whether or not to ignore if a default value for the + * specified path exists. + * @return True if this section contains the requested path, or if a default + * value exist and the boolean parameter for this method is true. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean contains(String path, boolean ignoreDefault); + + /** + * Checks if this {@link ConfigurationSection} has a value set for the + * given path. + *

+ * If the value for the requested path does not exist but a default value + * has been specified, this will still return false. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, regardless of + * having a default. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean isSet(String path); + + /** + * Gets the path of this {@link ConfigurationSection} from its root {@link + * Configuration} + *

+ * For any {@link Configuration} themselves, this will return an empty + * string. + *

+ * If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, this may return null. + *

+ * To retrieve the single name of this section, that is, the final part of + * the path returned by this method, you may use {@link #getName()}. + * + * @return Path of this section relative to its root + */ + public String getCurrentPath(); + + /** + * Gets the name of this individual {@link ConfigurationSection}, in the + * path. + *

+ * This will always be the final part of {@link #getCurrentPath()}, unless + * the section is orphaned. + * + * @return Name of this section + */ + public String getName(); + + /** + * Gets the root {@link Configuration} that contains this {@link + * ConfigurationSection} + *

+ * For any {@link Configuration} themselves, this will return its own + * object. + *

+ * If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, this may return null. + * + * @return Root configuration containing this section. + */ + public Configuration getRoot(); + + /** + * Gets the parent {@link ConfigurationSection} that directly contains + * this {@link ConfigurationSection}. + *

+ * For any {@link Configuration} themselves, this will return null. + *

+ * If the section is no longer contained within its parent for any reason, + * such as being replaced with a different value, this may return null. + * + * @return Parent section containing this section. + */ + public ConfigurationSection getParent(); + + /** + * Gets the requested Object by path. + *

+ * If the Object does not exist but a default value has been specified, + * this will return the default value. If the Object does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Object to get. + * @return Requested Object. + */ + public Object get(String path); + + /** + * Gets the requested Object by path, returning a default value if not + * found. + *

+ * If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Object to get. + * @param def The default value to return if the path is not found. + * @return Requested Object. + */ + public Object get(String path, Object def); + + /** + * Sets the specified path to the given value. + *

+ * If value is null, the entry will be removed. Any existing entry will be + * replaced, regardless of what the new value is. + *

+ * Some implementations may have limitations on what you may store. See + * their individual javadocs for details. No implementations should allow + * you to store {@link Configuration}s or {@link ConfigurationSection}s, + * please use {@link #createSection(String)} for that. + * + * @param path Path of the object to set. + * @param value New value to set the path to. + */ + public void set(String path, Object value); + + /** + * Creates an empty {@link ConfigurationSection} at the specified path. + *

+ * Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @return Newly created section + */ + public ConfigurationSection createSection(String path); + + /** + * Creates a {@link ConfigurationSection} at the specified path, with + * specified values. + *

+ * Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @param map The values to used. + * @return Newly created section + */ + public ConfigurationSection createSection(String path, Map map); + + // Primitives + /** + * Gets the requested String by path. + *

+ * If the String does not exist but a default value has been specified, + * this will return the default value. If the String does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the String to get. + * @return Requested String. + */ + public String getString(String path); + + /** + * Gets the requested String by path, returning a default value if not + * found. + *

+ * If the String does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the String to get. + * @param def The default value to return if the path is not found or is + * not a String. + * @return Requested String. + */ + public String getString(String path, String def); + + /** + * Checks if the specified path is a String. + *

+ * If the path exists but is not a String, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a String and return appropriately. + * + * @param path Path of the String to check. + * @return Whether or not the specified path is a String. + */ + public boolean isString(String path); + + /** + * Gets the requested int by path. + *

+ * If the int does not exist but a default value has been specified, this + * will return the default value. If the int does not exist and no default + * value was specified, this will return 0. + * + * @param path Path of the int to get. + * @return Requested int. + */ + public int getInt(String path); + + /** + * Gets the requested int by path, returning a default value if not found. + *

+ * If the int does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the int to get. + * @param def The default value to return if the path is not found or is + * not an int. + * @return Requested int. + */ + public int getInt(String path, int def); + + /** + * Checks if the specified path is an int. + *

+ * If the path exists but is not a int, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a int and return appropriately. + * + * @param path Path of the int to check. + * @return Whether or not the specified path is an int. + */ + public boolean isInt(String path); + + /** + * Gets the requested boolean by path. + *

+ * If the boolean does not exist but a default value has been specified, + * this will return the default value. If the boolean does not exist and + * no default value was specified, this will return false. + * + * @param path Path of the boolean to get. + * @return Requested boolean. + */ + public boolean getBoolean(String path); + + /** + * Gets the requested boolean by path, returning a default value if not + * found. + *

+ * If the boolean does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the boolean to get. + * @param def The default value to return if the path is not found or is + * not a boolean. + * @return Requested boolean. + */ + public boolean getBoolean(String path, boolean def); + + /** + * Checks if the specified path is a boolean. + *

+ * If the path exists but is not a boolean, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a boolean and return appropriately. + * + * @param path Path of the boolean to check. + * @return Whether or not the specified path is a boolean. + */ + public boolean isBoolean(String path); + + /** + * Gets the requested double by path. + *

+ * If the double does not exist but a default value has been specified, + * this will return the default value. If the double does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the double to get. + * @return Requested double. + */ + public double getDouble(String path); + + /** + * Gets the requested double by path, returning a default value if not + * found. + *

+ * If the double does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the double to get. + * @param def The default value to return if the path is not found or is + * not a double. + * @return Requested double. + */ + public double getDouble(String path, double def); + + /** + * Checks if the specified path is a double. + *

+ * If the path exists but is not a double, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a double and return appropriately. + * + * @param path Path of the double to check. + * @return Whether or not the specified path is a double. + */ + public boolean isDouble(String path); + + /** + * Gets the requested long by path. + *

+ * If the long does not exist but a default value has been specified, this + * will return the default value. If the long does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the long to get. + * @return Requested long. + */ + public long getLong(String path); + + /** + * Gets the requested long by path, returning a default value if not + * found. + *

+ * If the long does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the long to get. + * @param def The default value to return if the path is not found or is + * not a long. + * @return Requested long. + */ + public long getLong(String path, long def); + + /** + * Checks if the specified path is a long. + *

+ * If the path exists but is not a long, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a long and return appropriately. + * + * @param path Path of the long to check. + * @return Whether or not the specified path is a long. + */ + public boolean isLong(String path); + + // Java + /** + * Gets the requested List by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the List to get. + * @return Requested List. + */ + public List getList(String path); + + /** + * Gets the requested List by path, returning a default value if not + * found. + *

+ * If the List does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the List to get. + * @param def The default value to return if the path is not found or is + * not a List. + * @return Requested List. + */ + public List getList(String path, List def); + + /** + * Checks if the specified path is a List. + *

+ * If the path exists but is not a List, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a List and return appropriately. + * + * @param path Path of the List to check. + * @return Whether or not the specified path is a List. + */ + public boolean isList(String path); + + /** + * Gets the requested List of String by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a String if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of String. + */ + public List getStringList(String path); + + /** + * Gets the requested List of Integer by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Integer if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Integer. + */ + public List getIntegerList(String path); + + /** + * Gets the requested List of Boolean by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Boolean if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Boolean. + */ + public List getBooleanList(String path); + + /** + * Gets the requested List of Double by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Double if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Double. + */ + public List getDoubleList(String path); + + /** + * Gets the requested List of Float by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Float if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Float. + */ + public List getFloatList(String path); + + /** + * Gets the requested List of Long by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Long if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Long. + */ + public List getLongList(String path); + + /** + * Gets the requested List of Byte by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Byte if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Byte. + */ + public List getByteList(String path); + + /** + * Gets the requested List of Character by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Character if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Character. + */ + public List getCharacterList(String path); + + /** + * Gets the requested List of Short by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Short if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Short. + */ + public List getShortList(String path); + + /** + * Gets the requested List of Maps by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Map if possible, but + * may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Maps. + */ + public List> getMapList(String path); + + // Bukkit + /** + * Gets the requested {@link ConfigurationSerializable} object at the given + * path. + * + * If the Object does not exist but a default value has been specified, this + * will return the default value. If the Object does not exist and no + * default value was specified, this will return null. + * + * @param the type of {@link ConfigurationSerializable} + * @param path the path to the object. + * @param clazz the type of {@link ConfigurationSerializable} + * @return Requested {@link ConfigurationSerializable} object + */ + public T getSerializable(String path, Class clazz); + + /** + * Gets the requested {@link ConfigurationSerializable} object at the given + * path, returning a default value if not found + * + * If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param the type of {@link ConfigurationSerializable} + * @param path the path to the object. + * @param clazz the type of {@link ConfigurationSerializable} + * @param def the default object to return if the object is not present at + * the path + * @return Requested {@link ConfigurationSerializable} object + */ + public T getSerializable(String path, Class clazz, T def); + + /** + * Gets the requested Vector by path. + *

+ * If the Vector does not exist but a default value has been specified, + * this will return the default value. If the Vector does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Vector to get. + * @return Requested Vector. + */ + public Vector getVector(String path); + + /** + * Gets the requested {@link Vector} by path, returning a default value if + * not found. + *

+ * If the Vector does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Vector to get. + * @param def The default value to return if the path is not found or is + * not a Vector. + * @return Requested Vector. + */ + public Vector getVector(String path, Vector def); + + /** + * Checks if the specified path is a Vector. + *

+ * If the path exists but is not a Vector, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a Vector and return appropriately. + * + * @param path Path of the Vector to check. + * @return Whether or not the specified path is a Vector. + */ + public boolean isVector(String path); + + /** + * Gets the requested OfflinePlayer by path. + *

+ * If the OfflinePlayer does not exist but a default value has been + * specified, this will return the default value. If the OfflinePlayer + * does not exist and no default value was specified, this will return + * null. + * + * @param path Path of the OfflinePlayer to get. + * @return Requested OfflinePlayer. + */ + public OfflinePlayer getOfflinePlayer(String path); + + /** + * Gets the requested {@link OfflinePlayer} by path, returning a default + * value if not found. + *

+ * If the OfflinePlayer does not exist then the specified default value + * will returned regardless of if a default has been identified in the + * root {@link Configuration}. + * + * @param path Path of the OfflinePlayer to get. + * @param def The default value to return if the path is not found or is + * not an OfflinePlayer. + * @return Requested OfflinePlayer. + */ + public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def); + + /** + * Checks if the specified path is an OfflinePlayer. + *

+ * If the path exists but is not a OfflinePlayer, this will return false. + * If the path does not exist, this will return false. If the path does + * not exist but a default value has been specified, this will check if + * that default value is a OfflinePlayer and return appropriately. + * + * @param path Path of the OfflinePlayer to check. + * @return Whether or not the specified path is an OfflinePlayer. + */ + public boolean isOfflinePlayer(String path); + + /** + * Gets the requested ItemStack by path. + *

+ * If the ItemStack does not exist but a default value has been specified, + * this will return the default value. If the ItemStack does not exist and + * no default value was specified, this will return null. + * + * @param path Path of the ItemStack to get. + * @return Requested ItemStack. + */ + public ItemStack getItemStack(String path); + + /** + * Gets the requested {@link ItemStack} by path, returning a default value + * if not found. + *

+ * If the ItemStack does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the ItemStack to get. + * @param def The default value to return if the path is not found or is + * not an ItemStack. + * @return Requested ItemStack. + */ + public ItemStack getItemStack(String path, ItemStack def); + + /** + * Checks if the specified path is an ItemStack. + *

+ * If the path exists but is not a ItemStack, this will return false. If + * the path does not exist, this will return false. If the path does not + * exist but a default value has been specified, this will check if that + * default value is a ItemStack and return appropriately. + * + * @param path Path of the ItemStack to check. + * @return Whether or not the specified path is an ItemStack. + */ + public boolean isItemStack(String path); + + /** + * Gets the requested Color by path. + *

+ * If the Color does not exist but a default value has been specified, + * this will return the default value. If the Color does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Color to get. + * @return Requested Color. + */ + public Color getColor(String path); + + /** + * Gets the requested {@link Color} by path, returning a default value if + * not found. + *

+ * If the Color does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Color to get. + * @param def The default value to return if the path is not found or is + * not a Color. + * @return Requested Color. + */ + public Color getColor(String path, Color def); + + /** + * Checks if the specified path is a Color. + *

+ * If the path exists but is not a Color, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a Color and return appropriately. + * + * @param path Path of the Color to check. + * @return Whether or not the specified path is a Color. + */ + public boolean isColor(String path); + + /** + * Gets the requested ConfigurationSection by path. + *

+ * If the ConfigurationSection does not exist but a default value has been + * specified, this will return the default value. If the + * ConfigurationSection does not exist and no default value was specified, + * this will return null. + * + * @param path Path of the ConfigurationSection to get. + * @return Requested ConfigurationSection. + */ + public ConfigurationSection getConfigurationSection(String path); + + /** + * Checks if the specified path is a ConfigurationSection. + *

+ * If the path exists but is not a ConfigurationSection, this will return + * false. If the path does not exist, this will return false. If the path + * does not exist but a default value has been specified, this will check + * if that default value is a ConfigurationSection and return + * appropriately. + * + * @param path Path of the ConfigurationSection to check. + * @return Whether or not the specified path is a ConfigurationSection. + */ + public boolean isConfigurationSection(String path); + + /** + * Gets the equivalent {@link ConfigurationSection} from the default + * {@link Configuration} defined in {@link #getRoot()}. + *

+ * If the root contains no defaults, or the defaults doesn't contain a + * value for this path, or the value at this path is not a {@link + * ConfigurationSection} then this will return null. + * + * @return Equivalent section in root configuration + */ + public ConfigurationSection getDefaultSection(); + + /** + * Sets the default value in the root at the given path as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * If value is null, the value will be removed from the default + * Configuration source. + *

+ * If the value as returned by {@link #getDefaultSection()} is null, then + * this will create a new section at the path, replacing anything that may + * have existed there previously. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(String path, Object value); +} diff --git a/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java new file mode 100644 index 00000000..d23480e5 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java @@ -0,0 +1,45 @@ +package org.bukkit.configuration; + +/** + * Exception thrown when attempting to load an invalid {@link Configuration} + */ +@SuppressWarnings("serial") +public class InvalidConfigurationException extends Exception { + + /** + * Creates a new instance of InvalidConfigurationException without a + * message or cause. + */ + public InvalidConfigurationException() {} + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message. + * + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg) { + super(msg); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified cause. + * + * @param cause The cause of the exception. + */ + public InvalidConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message and cause. + * + * @param cause The cause of the exception. + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/bukkit/configuration/MemoryConfiguration.java b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java new file mode 100644 index 00000000..670e4cda --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java @@ -0,0 +1,79 @@ +package org.bukkit.configuration; + +import java.util.Map; + +import org.apache.commons.lang3.Validate; + +/** + * This is a {@link Configuration} implementation that does not save or load + * from any source, and stores all values in memory only. + * This is useful for temporary Configurations for providing defaults. + */ +public class MemoryConfiguration extends MemorySection implements Configuration { + protected Configuration defaults; + protected MemoryConfigurationOptions options; + + /** + * Creates an empty {@link MemoryConfiguration} with no default values. + */ + public MemoryConfiguration() {} + + /** + * Creates an empty {@link MemoryConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + * @throws IllegalArgumentException Thrown if defaults is null + */ + public MemoryConfiguration(Configuration defaults) { + this.defaults = defaults; + } + + @Override + public void addDefault(String path, Object value) { + Validate.notNull(path, "Path may not be null"); + + if (defaults == null) { + defaults = new MemoryConfiguration(); + } + + defaults.set(path, value); + } + + public void addDefaults(Map defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + for (Map.Entry entry : defaults.entrySet()) { + addDefault(entry.getKey(), entry.getValue()); + } + } + + public void addDefaults(Configuration defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + addDefaults(defaults.getValues(true)); + } + + public void setDefaults(Configuration defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + this.defaults = defaults; + } + + public Configuration getDefaults() { + return defaults; + } + + @Override + public ConfigurationSection getParent() { + return null; + } + + public MemoryConfigurationOptions options() { + if (options == null) { + options = new MemoryConfigurationOptions(this); + } + + return options; + } +} diff --git a/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java new file mode 100644 index 00000000..44c046c4 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java @@ -0,0 +1,28 @@ +package org.bukkit.configuration; + +/** + * Various settings for controlling the input and output of a {@link + * MemoryConfiguration} + */ +public class MemoryConfigurationOptions extends ConfigurationOptions { + protected MemoryConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override + public MemoryConfiguration configuration() { + return (MemoryConfiguration) super.configuration(); + } + + @Override + public MemoryConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public MemoryConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/MemorySection.java b/src/main/java/org/bukkit/configuration/MemorySection.java new file mode 100644 index 00000000..025be683 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemorySection.java @@ -0,0 +1,831 @@ +package org.bukkit.configuration; + +import static org.bukkit.util.NumberConversions.*; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +/** + * A type of {@link ConfigurationSection} that is stored in memory. + */ +public class MemorySection implements ConfigurationSection { + protected final Map map = new LinkedHashMap(); + private final Configuration root; + private final ConfigurationSection parent; + private final String path; + private final String fullPath; + + /** + * Creates an empty MemorySection for use as a root {@link Configuration} + * section. + *

+ * Note that calling this without being yourself a {@link Configuration} + * will throw an exception! + * + * @throws IllegalStateException Thrown if this is not a {@link + * Configuration} root. + */ + protected MemorySection() { + if (!(this instanceof Configuration)) { + throw new IllegalStateException("Cannot construct a root MemorySection when not a Configuration"); + } + + this.path = ""; + this.fullPath = ""; + this.parent = null; + this.root = (Configuration) this; + } + + /** + * Creates an empty MemorySection with the specified parent and path. + * + * @param parent Parent section that contains this own section. + * @param path Path that you may access this section from via the root + * {@link Configuration}. + * @throws IllegalArgumentException Thrown is parent or path is null, or + * if parent contains no root Configuration. + */ + protected MemorySection(ConfigurationSection parent, String path) { + Validate.notNull(parent, "Parent cannot be null"); + Validate.notNull(path, "Path cannot be null"); + + this.path = path; + this.parent = parent; + this.root = parent.getRoot(); + + Validate.notNull(root, "Path cannot be orphaned"); + + this.fullPath = createPath(parent, path); + } + + public Set getKeys(boolean deep) { + Set result = new LinkedHashSet(); + + Configuration root = getRoot(); + if (root != null && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.addAll(defaults.getKeys(deep)); + } + } + + mapChildrenKeys(result, this, deep); + + return result; + } + + public Map getValues(boolean deep) { + Map result = new LinkedHashMap(); + + Configuration root = getRoot(); + if (root != null && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.putAll(defaults.getValues(deep)); + } + } + + mapChildrenValues(result, this, deep); + + return result; + } + + public boolean contains(String path) { + return contains(path, false); + } + + public boolean contains(String path, boolean ignoreDefault) { + return ((ignoreDefault) ? get(path, null) : get(path)) != null; + } + + public boolean isSet(String path) { + Configuration root = getRoot(); + if (root == null) { + return false; + } + if (root.options().copyDefaults()) { + return contains(path); + } + return get(path, null) != null; + } + + public String getCurrentPath() { + return fullPath; + } + + public String getName() { + return path; + } + + public Configuration getRoot() { + return root; + } + + public ConfigurationSection getParent() { + return parent; + } + + public void addDefault(String path, Object value) { + Validate.notNull(path, "Path cannot be null"); + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot add default without root"); + } + if (root == this) { + throw new UnsupportedOperationException("Unsupported addDefault(String, Object) implementation"); + } + root.addDefault(createPath(this, path), value); + } + + public ConfigurationSection getDefaultSection() { + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + + if (defaults != null) { + if (defaults.isConfigurationSection(getCurrentPath())) { + return defaults.getConfigurationSection(getCurrentPath()); + } + } + + return null; + } + + public void set(String path, Object value) { + Validate.notEmpty(path, "Cannot set to an empty path"); + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot use section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + if (value == null) { + // no need to create missing sub-sections if we want to remove the value: + return; + } + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + if (value == null) { + map.remove(key); + } else { + map.put(key, value); + } + } else { + section.set(key, value); + } + } + + public Object get(String path) { + return get(path, getDefault(path)); + } + + public Object get(String path, Object def) { + Validate.notNull(path, "Path cannot be null"); + + if (path.length() == 0) { + return this; + } + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot access section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + section = section.getConfigurationSection(path.substring(i2, i1)); + if (section == null) { + return def; + } + } + + String key = path.substring(i2); + if (section == this) { + Object result = map.get(key); + return (result == null) ? def : result; + } + return section.get(key, def); + } + + public ConfigurationSection createSection(String path) { + Validate.notEmpty(path, "Cannot create section at empty path"); + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + ConfigurationSection result = new MemorySection(this, key); + map.put(key, result); + return result; + } + return section.createSection(key); + } + + public ConfigurationSection createSection(String path, Map map) { + ConfigurationSection section = createSection(path); + + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Map) { + section.createSection(entry.getKey().toString(), (Map) entry.getValue()); + } else { + section.set(entry.getKey().toString(), entry.getValue()); + } + } + + return section; + } + + // Primitives + public String getString(String path) { + Object def = getDefault(path); + return getString(path, def != null ? def.toString() : null); + } + + public String getString(String path, String def) { + Object val = get(path, def); + return (val != null) ? val.toString() : def; + } + + public boolean isString(String path) { + Object val = get(path); + return val instanceof String; + } + + public int getInt(String path) { + Object def = getDefault(path); + return getInt(path, (def instanceof Number) ? toInt(def) : 0); + } + + public int getInt(String path, int def) { + Object val = get(path, def); + return (val instanceof Number) ? toInt(val) : def; + } + + public boolean isInt(String path) { + Object val = get(path); + return val instanceof Integer; + } + + public boolean getBoolean(String path) { + Object def = getDefault(path); + return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false); + } + + public boolean getBoolean(String path, boolean def) { + Object val = get(path, def); + return (val instanceof Boolean) ? (Boolean) val : def; + } + + public boolean isBoolean(String path) { + Object val = get(path); + return val instanceof Boolean; + } + + public double getDouble(String path) { + Object def = getDefault(path); + return getDouble(path, (def instanceof Number) ? toDouble(def) : 0); + } + + public double getDouble(String path, double def) { + Object val = get(path, def); + return (val instanceof Number) ? toDouble(val) : def; + } + + public boolean isDouble(String path) { + Object val = get(path); + return val instanceof Double; + } + + public long getLong(String path) { + Object def = getDefault(path); + return getLong(path, (def instanceof Number) ? toLong(def) : 0); + } + + public long getLong(String path, long def) { + Object val = get(path, def); + return (val instanceof Number) ? toLong(val) : def; + } + + public boolean isLong(String path) { + Object val = get(path); + return val instanceof Long; + } + + // Java + public List getList(String path) { + Object def = getDefault(path); + return getList(path, (def instanceof List) ? (List) def : null); + } + + public List getList(String path, List def) { + Object val = get(path, def); + return (List) ((val instanceof List) ? val : def); + } + + public boolean isList(String path) { + Object val = get(path); + return val instanceof List; + } + + public List getStringList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if ((object instanceof String) || (isPrimitiveWrapper(object))) { + result.add(String.valueOf(object)); + } + } + + return result; + } + + public List getIntegerList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Integer) { + result.add((Integer) object); + } else if (object instanceof String) { + try { + result.add(Integer.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((int) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).intValue()); + } + } + + return result; + } + + public List getBooleanList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Boolean) { + result.add((Boolean) object); + } else if (object instanceof String) { + if (Boolean.TRUE.toString().equals(object)) { + result.add(true); + } else if (Boolean.FALSE.toString().equals(object)) { + result.add(false); + } + } + } + + return result; + } + + public List getDoubleList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Double) { + result.add((Double) object); + } else if (object instanceof String) { + try { + result.add(Double.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((double) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).doubleValue()); + } + } + + return result; + } + + public List getFloatList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Float) { + result.add((Float) object); + } else if (object instanceof String) { + try { + result.add(Float.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((float) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).floatValue()); + } + } + + return result; + } + + public List getLongList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Long) { + result.add((Long) object); + } else if (object instanceof String) { + try { + result.add(Long.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((long) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).longValue()); + } + } + + return result; + } + + public List getByteList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Byte) { + result.add((Byte) object); + } else if (object instanceof String) { + try { + result.add(Byte.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((byte) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).byteValue()); + } + } + + return result; + } + + public List getCharacterList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Character) { + result.add((Character) object); + } else if (object instanceof String) { + String str = (String) object; + + if (str.length() == 1) { + result.add(str.charAt(0)); + } + } else if (object instanceof Number) { + result.add((char) ((Number) object).intValue()); + } + } + + return result; + } + + public List getShortList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Short) { + result.add((Short) object); + } else if (object instanceof String) { + try { + result.add(Short.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((short) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).shortValue()); + } + } + + return result; + } + + public List> getMapList(String path) { + List list = getList(path); + List> result = new ArrayList>(); + + if (list == null) { + return result; + } + + for (Object object : list) { + if (object instanceof Map) { + result.add((Map) object); + } + } + + return result; + } + + // Bukkit + @Override + public T getSerializable(String path, Class clazz) { + Validate.notNull(clazz, "ConfigurationSerializable class cannot be null"); + Object def = getDefault(path); + return getSerializable(path, clazz, (def != null && clazz.isInstance(def)) ? clazz.cast(def) : null); + } + + @Override + public T getSerializable(String path, Class clazz, T def) { + Validate.notNull(clazz, "ConfigurationSerializable class cannot be null"); + Object val = get(path); + return (val != null && clazz.isInstance(val)) ? clazz.cast(val) : def; + } + + public Vector getVector(String path) { + return getSerializable(path, Vector.class); + } + + public Vector getVector(String path, Vector def) { + return getSerializable(path, Vector.class, def); + } + + public boolean isVector(String path) { + return getSerializable(path, Vector.class) != null; + } + + public OfflinePlayer getOfflinePlayer(String path) { + return getSerializable(path, OfflinePlayer.class); + } + + public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def) { + return getSerializable(path, OfflinePlayer.class, def); + } + + public boolean isOfflinePlayer(String path) { + return getSerializable(path, OfflinePlayer.class) != null; + } + + public ItemStack getItemStack(String path) { + return getSerializable(path, ItemStack.class); + } + + public ItemStack getItemStack(String path, ItemStack def) { + return getSerializable(path, ItemStack.class, def); + } + + public boolean isItemStack(String path) { + return getSerializable(path, ItemStack.class) != null; + } + + public Color getColor(String path) { + return getSerializable(path, Color.class); + } + + public Color getColor(String path, Color def) { + return getSerializable(path, Color.class, def); + } + + public boolean isColor(String path) { + return getSerializable(path, Color.class) != null; + } + + public ConfigurationSection getConfigurationSection(String path) { + Object val = get(path, null); + if (val != null) { + return (val instanceof ConfigurationSection) ? (ConfigurationSection) val : null; + } + + val = get(path, getDefault(path)); + return (val instanceof ConfigurationSection) ? createSection(path) : null; + } + + public boolean isConfigurationSection(String path) { + Object val = get(path); + return val instanceof ConfigurationSection; + } + + protected boolean isPrimitiveWrapper(Object input) { + return input instanceof Integer || input instanceof Boolean || + input instanceof Character || input instanceof Byte || + input instanceof Short || input instanceof Double || + input instanceof Long || input instanceof Float; + } + + protected Object getDefault(String path) { + Validate.notNull(path, "Path cannot be null"); + + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + return (defaults == null) ? null : defaults.get(createPath(this, path)); + } + + protected void mapChildrenKeys(Set output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + output.add(createPath(section, entry.getKey(), this)); + + if ((deep) && (entry.getValue() instanceof ConfigurationSection)) { + ConfigurationSection subsection = (ConfigurationSection) entry.getValue(); + mapChildrenKeys(output, subsection, deep); + } + } + } else { + Set keys = section.getKeys(deep); + + for (String key : keys) { + output.add(createPath(section, key, this)); + } + } + } + + protected void mapChildrenValues(Map output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + + if (entry.getValue() instanceof ConfigurationSection) { + if (deep) { + mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep); + } + } + } + } else { + Map values = section.getValues(deep); + + for (Map.Entry entry : values.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + } + } + } + + /** + * Creates a full path to the given {@link ConfigurationSection} from its + * root {@link Configuration}. + *

+ * You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key) { + return createPath(section, key, (section == null) ? null : section.getRoot()); + } + + /** + * Creates a relative path to the given {@link ConfigurationSection} from + * the given relative section. + *

+ * You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @param relativeTo Section to create the path relative to. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key, ConfigurationSection relativeTo) { + Validate.notNull(section, "Cannot create path without a section"); + Configuration root = section.getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create path without a root"); + } + char separator = root.options().pathSeparator(); + + StringBuilder builder = new StringBuilder(); + if (section != null) { + for (ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) { + if (builder.length() > 0) { + builder.insert(0, separator); + } + + builder.insert(0, parent.getName()); + } + } + + if ((key != null) && (key.length() > 0)) { + if (builder.length() > 0) { + builder.append(separator); + } + + builder.append(key); + } + + return builder.toString(); + } + + @Override + public String toString() { + Configuration root = getRoot(); + return new StringBuilder() + .append(getClass().getSimpleName()) + .append("[path='") + .append(getCurrentPath()) + .append("', root='") + .append(root == null ? null : root.getClass().getSimpleName()) + .append("']") + .toString(); + } +} diff --git a/src/main/java/org/bukkit/configuration/file/FileConfiguration.java b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java new file mode 100644 index 00000000..f0a1cf11 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java @@ -0,0 +1,224 @@ +package org.bukkit.configuration.file; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; + +import org.apache.commons.lang3.Validate; +import org.bukkit.configuration.InvalidConfigurationException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.MemoryConfiguration; + +/** + * This is a base class for all File based implementations of {@link + * Configuration} + */ +public abstract class FileConfiguration extends MemoryConfiguration { + + /** + * Creates an empty {@link FileConfiguration} with no default values. + */ + public FileConfiguration() { + super(); + } + + /** + * Creates an empty {@link FileConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + */ + public FileConfiguration(Configuration defaults) { + super(defaults); + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + *

+ * If the file does not exist, it will be created. If already exists, it + * will be overwritten. If it cannot be overwritten or created, an + * exception will be thrown. + *

+ * This method will save using the system default encoding, or possibly + * using UTF8. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for + * any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(File file) throws IOException { + Validate.notNull(file, "File cannot be null"); + + Files.createParentDirs(file); + + String data = saveToString(); + + Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8); + + try { + writer.write(data); + } finally { + writer.close(); + } + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + *

+ * If the file does not exist, it will be created. If already exists, it + * will be overwritten. If it cannot be overwritten or created, an + * exception will be thrown. + *

+ * This method will save using the system default encoding, or possibly + * using UTF8. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for + * any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(String file) throws IOException { + Validate.notNull(file, "File cannot be null"); + + save(new File(file)); + } + + /** + * Saves this {@link FileConfiguration} to a string, and returns it. + * + * @return String containing this configuration. + */ + public abstract String saveToString(); + + /** + * Loads this {@link FileConfiguration} from the specified location. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given file. + *

+ * If the file cannot be loaded for any reason, an exception will be + * thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be + * opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not + * a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException { + Validate.notNull(file, "File cannot be null"); + + final FileInputStream stream = new FileInputStream(file); + + load(new InputStreamReader(stream, Charsets.UTF_8)); + } + + /** + * Loads this {@link FileConfiguration} from the specified reader. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given stream. + * + * @param reader the reader to load from + * @throws IOException thrown when underlying reader throws an IOException + * @throws InvalidConfigurationException thrown when the reader does not + * represent a valid Configuration + * @throws IllegalArgumentException thrown when reader is null + */ + public void load(Reader reader) throws IOException, InvalidConfigurationException { + BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + + StringBuilder builder = new StringBuilder(); + + try { + String line; + + while ((line = input.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + } finally { + input.close(); + } + + loadFromString(builder.toString()); + } + + /** + * Loads this {@link FileConfiguration} from the specified location. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given file. + *

+ * If the file cannot be loaded for any reason, an exception will be + * thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be + * opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not + * a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(String file) throws FileNotFoundException, IOException, InvalidConfigurationException { + Validate.notNull(file, "File cannot be null"); + + load(new File(file)); + } + + /** + * Loads this {@link FileConfiguration} from the specified string, as + * opposed to from file. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given string. + *

+ * If the string is invalid in any way, an exception will be thrown. + * + * @param contents Contents of a Configuration to load. + * @throws InvalidConfigurationException Thrown if the specified string is + * invalid. + * @throws IllegalArgumentException Thrown if contents is null. + */ + public abstract void loadFromString(String contents) throws InvalidConfigurationException; + + /** + * Compiles the header for this {@link FileConfiguration} and returns the + * result. + *

+ * This will use the header from {@link #options()} -> {@link + * FileConfigurationOptions#header()}, respecting the rules of {@link + * FileConfigurationOptions#copyHeader()} if set. + * + * @return Compiled header + */ + protected abstract String buildHeader(); + + @Override + public FileConfigurationOptions options() { + if (options == null) { + options = new FileConfigurationOptions(this); + } + + return (FileConfigurationOptions) options; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java new file mode 100644 index 00000000..c4be8890 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java @@ -0,0 +1,118 @@ +package org.bukkit.configuration.file; + +import org.bukkit.configuration.*; + +/** + * Various settings for controlling the input and output of a {@link + * FileConfiguration} + */ +public class FileConfigurationOptions extends MemoryConfigurationOptions { + private String header = null; + private boolean copyHeader = true; + + protected FileConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override + public FileConfiguration configuration() { + return (FileConfiguration) super.configuration(); + } + + @Override + public FileConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public FileConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + /** + * Gets the header that will be applied to the top of the saved output. + *

+ * This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + *

+ * Null is a valid value which will indicate that no header is to be + * applied. The default value is null. + * + * @return Header + */ + public String header() { + return header; + } + + /** + * Sets the header that will be applied to the top of the saved output. + *

+ * This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + *

+ * Null is a valid value which will indicate that no header is to be + * applied. + * + * @param value New header + * @return This object, for chaining + */ + public FileConfigurationOptions header(String value) { + this.header = value; + return this; + } + + /** + * Gets whether or not the header should be copied from a default source. + *

+ * If this is true, if a default {@link FileConfiguration} is passed to + * {@link + * FileConfiguration#setDefaults(Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + *

+ * If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + *

+ * Defaults to true. + * + * @return Whether or not to copy the header + */ + public boolean copyHeader() { + return copyHeader; + } + + /** + * Sets whether or not the header should be copied from a default source. + *

+ * If this is true, if a default {@link FileConfiguration} is passed to + * {@link + * FileConfiguration#setDefaults(Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + *

+ * If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + *

+ * Defaults to true. + * + * @param value Whether or not to copy the header + * @return This object, for chaining + */ + public FileConfigurationOptions copyHeader(boolean value) { + copyHeader = value; + + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java new file mode 100644 index 00000000..00c9ac40 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java @@ -0,0 +1,215 @@ +package org.bukkit.configuration.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; +import java.util.Map; +import java.util.logging.Level; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.representer.Representer; + +/** + * An implementation of {@link Configuration} which saves all files in Yaml. + * Note that this implementation is not synchronized. + */ +public class YamlConfiguration extends FileConfiguration { + protected static final String COMMENT_PREFIX = "# "; + protected static final String BLANK_CONFIG = "{}\n"; + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new YamlRepresenter(); + private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); + + @Override + public String saveToString() { + yamlOptions.setIndent(options().indent()); + yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + String header = buildHeader(); + String dump = yaml.dump(getValues(false)); + + if (dump.equals(BLANK_CONFIG)) { + dump = ""; + } + + return header + dump; + } + + @Override + public void loadFromString(String contents) throws InvalidConfigurationException { + Validate.notNull(contents, "Contents cannot be null"); + + Map input; + try { + input = (Map) yaml.load(contents); + } catch (YAMLException e) { + throw new InvalidConfigurationException(e); + } catch (ClassCastException e) { + throw new InvalidConfigurationException("Top level is not a Map."); + } + + String header = parseHeader(contents); + if (header.length() > 0) { + options().header(header); + } + + if (input != null) { + convertMapsToSections(input, this); + } + } + + protected void convertMapsToSections(Map input, ConfigurationSection section) { + for (Map.Entry entry : input.entrySet()) { + String key = entry.getKey().toString(); + Object value = entry.getValue(); + + if (value instanceof Map) { + convertMapsToSections((Map) value, section.createSection(key)); + } else { + section.set(key, value); + } + } + } + + protected String parseHeader(String input) { + String[] lines = input.split("\r?\n", -1); + StringBuilder result = new StringBuilder(); + boolean readingHeader = true; + boolean foundHeader = false; + + for (int i = 0; (i < lines.length) && (readingHeader); i++) { + String line = lines[i]; + + if (line.startsWith(COMMENT_PREFIX)) { + if (i > 0) { + result.append("\n"); + } + + if (line.length() > COMMENT_PREFIX.length()) { + result.append(line.substring(COMMENT_PREFIX.length())); + } + + foundHeader = true; + } else if ((foundHeader) && (line.length() == 0)) { + result.append("\n"); + } else if (foundHeader) { + readingHeader = false; + } + } + + return result.toString(); + } + + @Override + protected String buildHeader() { + String header = options().header(); + + if (options().copyHeader()) { + Configuration def = getDefaults(); + + if ((def != null) && (def instanceof FileConfiguration)) { + FileConfiguration filedefaults = (FileConfiguration) def; + String defaultsHeader = filedefaults.buildHeader(); + + if ((defaultsHeader != null) && (defaultsHeader.length() > 0)) { + return defaultsHeader; + } + } + } + + if (header == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + String[] lines = header.split("\r?\n", -1); + boolean startedHeader = false; + + for (int i = lines.length - 1; i >= 0; i--) { + builder.insert(0, "\n"); + + if ((startedHeader) || (lines[i].length() != 0)) { + builder.insert(0, lines[i]); + builder.insert(0, COMMENT_PREFIX); + startedHeader = true; + } + } + + return builder.toString(); + } + + @Override + public YamlConfigurationOptions options() { + if (options == null) { + options = new YamlConfigurationOptions(this); + } + + return (YamlConfigurationOptions) options; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given file. + *

+ * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be + * returned. + *

+ * The encoding used may follow the system dependent default. + * + * @param file Input file + * @return Resulting configuration + * @throws IllegalArgumentException Thrown if file is null + */ + public static YamlConfiguration loadConfiguration(File file) { + Validate.notNull(file, "File cannot be null"); + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(file); + } catch (FileNotFoundException ex) { + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } + + return config; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given reader. + *

+ * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be + * returned. + * + * @param reader input + * @return resulting configuration + * @throws IllegalArgumentException Thrown if stream is null + */ + public static YamlConfiguration loadConfiguration(Reader reader) { + Validate.notNull(reader, "Stream cannot be null"); + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(reader); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); + } + + return config; + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java new file mode 100644 index 00000000..2a6c1f89 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java @@ -0,0 +1,71 @@ +package org.bukkit.configuration.file; + +import org.apache.commons.lang3.Validate; + +/** + * Various settings for controlling the input and output of a {@link + * YamlConfiguration} + */ +public class YamlConfigurationOptions extends FileConfigurationOptions { + private int indent = 2; + + protected YamlConfigurationOptions(YamlConfiguration configuration) { + super(configuration); + } + + @Override + public YamlConfiguration configuration() { + return (YamlConfiguration) super.configuration(); + } + + @Override + public YamlConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public YamlConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + @Override + public YamlConfigurationOptions header(String value) { + super.header(value); + return this; + } + + @Override + public YamlConfigurationOptions copyHeader(boolean value) { + super.copyHeader(value); + return this; + } + + /** + * Gets how much spaces should be used to indent each line. + *

+ * The minimum value this may be is 2, and the maximum is 9. + * + * @return How much to indent by + */ + public int indent() { + return indent; + } + + /** + * Sets how much spaces should be used to indent each line. + *

+ * The minimum value this may be is 2, and the maximum is 9. + * + * @param value New indent + * @return This object, for chaining + */ + public YamlConfigurationOptions indent(int value) { + Validate.isTrue(value >= 2, "Indent must be at least 2 characters"); + Validate.isTrue(value <= 9, "Indent cannot be greater than 9 characters"); + + this.indent = value; + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlConstructor.java b/src/main/java/org/bukkit/configuration/file/YamlConstructor.java new file mode 100644 index 00000000..73ad722a --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlConstructor.java @@ -0,0 +1,49 @@ +package org.bukkit.configuration.file; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.nodes.Tag; + +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +public class YamlConstructor extends SafeConstructor { + + public YamlConstructor() { + this.yamlConstructors.put(Tag.MAP, new ConstructCustomObject()); + } + + private class ConstructCustomObject extends ConstructYamlMap { + @Override + public Object construct(Node node) { + if (node.isTwoStepsConstruction()) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + + Map raw = (Map) super.construct(node); + + if (raw.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { + Map typed = new LinkedHashMap(raw.size()); + for (Map.Entry entry : raw.entrySet()) { + typed.put(entry.getKey().toString(), entry.getValue()); + } + + try { + return ConfigurationSerialization.deserializeObject(typed); + } catch (IllegalArgumentException ex) { + throw new YAMLException("Could not deserialize object", ex); + } + } + + return raw; + } + + @Override + public void construct2ndStep(Node node, Object object) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java b/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java new file mode 100644 index 00000000..bc9c098d --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java @@ -0,0 +1,38 @@ +package org.bukkit.configuration.file; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.representer.Representer; + +public class YamlRepresenter extends Representer { + + public YamlRepresenter() { + this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection()); + this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable()); + } + + private class RepresentConfigurationSection extends RepresentMap { + @Override + public Node representData(Object data) { + return super.representData(((ConfigurationSection) data).getValues(false)); + } + } + + private class RepresentConfigurationSerializable extends RepresentMap { + @Override + public Node representData(Object data) { + ConfigurationSerializable serializable = (ConfigurationSerializable) data; + Map values = new LinkedHashMap(); + values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass())); + values.putAll(serializable.serialize()); + + return super.representData(values); + } + } +} diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java new file mode 100644 index 00000000..74b73f9d --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java @@ -0,0 +1,35 @@ +package org.bukkit.configuration.serialization; + +import java.util.Map; + +/** + * Represents an object that may be serialized. + *

+ * These objects MUST implement one of the following, in addition to the + * methods as defined by this interface: + *

    + *
  • A static method "deserialize" that accepts a single {@link Map}< + * {@link String}, {@link Object}> and returns the class.
  • + *
  • A static method "valueOf" that accepts a single {@link Map}<{@link + * String}, {@link Object}> and returns the class.
  • + *
  • A constructor that accepts a single {@link Map}<{@link String}, + * {@link Object}>.
  • + *
+ * In addition to implementing this interface, you must register the class + * with {@link ConfigurationSerialization#registerClass(Class)}. + * + * @see DelegateDeserialization + * @see SerializableAs + */ +public interface ConfigurationSerializable { + + /** + * Creates a Map representation of this class. + *

+ * This class must provide a method to restore this class, as defined in + * the {@link ConfigurationSerializable} interface javadocs. + * + * @return Map containing the current state of this class + */ + public Map serialize(); +} diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java new file mode 100644 index 00000000..7b8e1ccb --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java @@ -0,0 +1,287 @@ +package org.bukkit.configuration.serialization; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.Location; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.banner.Pattern; +import org.bukkit.configuration.Configuration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +/** + * Utility class for storing and retrieving classes for {@link Configuration}. + */ +public class ConfigurationSerialization { + public static final String SERIALIZED_TYPE_KEY = "=="; + private final Class clazz; + private static Map> aliases = new HashMap>(); + + static { + registerClass(Vector.class); + registerClass(BlockVector.class); + registerClass(ItemStack.class); + registerClass(Color.class); + registerClass(PotionEffect.class); + registerClass(FireworkEffect.class); + registerClass(Pattern.class); + registerClass(Location.class); + registerClass(AttributeModifier.class); + } + + protected ConfigurationSerialization(Class clazz) { + this.clazz = clazz; + } + + protected Method getMethod(String name, boolean isStatic) { + try { + Method method = clazz.getDeclaredMethod(name, Map.class); + + if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) { + return null; + } + if (Modifier.isStatic(method.getModifiers()) != isStatic) { + return null; + } + + return method; + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + protected Constructor getConstructor() { + try { + return clazz.getConstructor(Map.class); + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + protected ConfigurationSerializable deserializeViaMethod(Method method, Map args) { + try { + ConfigurationSerializable result = (ConfigurationSerializable) method.invoke(null, args); + + if (result == null) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null"); + } else { + return result; + } + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log( + Level.SEVERE, + "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + protected ConfigurationSerializable deserializeViaCtor(Constructor ctor, Map args) { + try { + return ctor.newInstance(args); + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log( + Level.SEVERE, + "Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + public ConfigurationSerializable deserialize(Map args) { + Validate.notNull(args, "Args must not be null"); + + ConfigurationSerializable result = null; + Method method = null; + + if (result == null) { + method = getMethod("deserialize", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + method = getMethod("valueOf", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + Constructor constructor = getConstructor(); + + if (constructor != null) { + result = deserializeViaCtor(constructor, args); + } + } + + return result; + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + * given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable. + *

+ * If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned. + * + * @param args Arguments for deserialization + * @param clazz Class to deserialize into + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map args, Class clazz) { + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + * given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable. + *

+ * If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned. + * + * @param args Arguments for deserialization + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map args) { + Class clazz = null; + + if (args.containsKey(SERIALIZED_TYPE_KEY)) { + try { + String alias = (String) args.get(SERIALIZED_TYPE_KEY); + + if (alias == null) { + throw new IllegalArgumentException("Cannot have null alias"); + } + clazz = getClassByAlias(alias); + if (clazz == null) { + throw new IllegalArgumentException("Specified class does not exist ('" + alias + "')"); + } + } catch (ClassCastException ex) { + ex.fillInStackTrace(); + throw ex; + } + } else { + throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')"); + } + + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Registers the given {@link ConfigurationSerializable} class by its + * alias + * + * @param clazz Class to register + */ + public static void registerClass(Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate == null) { + registerClass(clazz, getAlias(clazz)); + registerClass(clazz, clazz.getName()); + } + } + + /** + * Registers the given alias to the specified {@link + * ConfigurationSerializable} class + * + * @param clazz Class to register + * @param alias Alias to register as + * @see SerializableAs + */ + public static void registerClass(Class clazz, String alias) { + aliases.put(alias, clazz); + } + + /** + * Unregisters the specified alias to a {@link ConfigurationSerializable} + * + * @param alias Alias to unregister + */ + public static void unregisterClass(String alias) { + aliases.remove(alias); + } + + /** + * Unregisters any aliases for the specified {@link + * ConfigurationSerializable} class + * + * @param clazz Class to unregister + */ + public static void unregisterClass(Class clazz) { + while (aliases.values().remove(clazz)) { + ; + } + } + + /** + * Attempts to get a registered {@link ConfigurationSerializable} class by + * its alias + * + * @param alias Alias of the serializable + * @return Registered class, or null if not found + */ + public static Class getClassByAlias(String alias) { + return aliases.get(alias); + } + + /** + * Gets the correct alias for the given {@link ConfigurationSerializable} + * class + * + * @param clazz Class to get alias for + * @return Alias to use for the class + */ + public static String getAlias(Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate != null) { + if ((delegate.value() == null) || (delegate.value() == clazz)) { + delegate = null; + } else { + return getAlias(delegate.value()); + } + } + + if (delegate == null) { + SerializableAs alias = clazz.getAnnotation(SerializableAs.class); + + if ((alias != null) && (alias.value() != null)) { + return alias.value(); + } + } + + return clazz.getName(); + } +} diff --git a/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java new file mode 100644 index 00000000..1cfae94f --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java @@ -0,0 +1,22 @@ +package org.bukkit.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Applies to a {@link ConfigurationSerializable} that will delegate all + * deserialization to another {@link ConfigurationSerializable}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DelegateDeserialization { + /** + * Which class should be used as a delegate for this classes + * deserialization + * + * @return Delegate class + */ + public Class value(); +} diff --git a/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java new file mode 100644 index 00000000..c5ee9987 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java @@ -0,0 +1,34 @@ +package org.bukkit.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents an "alias" that a {@link ConfigurationSerializable} may be + * stored as. + * If this is not present on a {@link ConfigurationSerializable} class, it + * will use the fully qualified name of the class. + *

+ * This value will be stored in the configuration so that the configuration + * deserialization can determine what type it is. + *

+ * Using this annotation on any other class than a {@link + * ConfigurationSerializable} will have no effect. + * + * @see ConfigurationSerialization#registerClass(Class, String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SerializableAs { + /** + * This is the name your class will be stored and retrieved as. + *

+ * This name MUST be unique. We recommend using names such as + * "MyPluginThing" instead of "Thing". + * + * @return Name to serialize the class as. + */ + public String value(); +} diff --git a/src/main/java/org/bukkit/conversations/BooleanPrompt.java b/src/main/java/org/bukkit/conversations/BooleanPrompt.java new file mode 100644 index 00000000..b20c5488 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/BooleanPrompt.java @@ -0,0 +1,37 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.BooleanUtils; + +/** + * BooleanPrompt is the base class for any prompt that requires a boolean + * response from the user. + */ +public abstract class BooleanPrompt extends ValidatingPrompt { + + public BooleanPrompt() { + super(); + } + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + String[] accepted = {"true", "false", "on", "off", "yes", "no" /* Spigot: */, "y", "n", "1", "0", "right", "wrong", "correct", "incorrect", "valid", "invalid"}; // Spigot + return ArrayUtils.contains(accepted, input.toLowerCase()); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + if (input.equalsIgnoreCase("y") || input.equals("1") || input.equalsIgnoreCase("right") || input.equalsIgnoreCase("correct") || input.equalsIgnoreCase("valid")) input = "true"; // Spigot + return acceptValidatedInput(context, BooleanUtils.toBoolean(input)); + } + + /** + * Override this method to perform some action with the user's boolean + * response. + * + * @param context Context information about the conversation. + * @param input The user's boolean response. + * @return The next {@link Prompt} in the prompt graph. + */ + protected abstract Prompt acceptValidatedInput(ConversationContext context, boolean input); +} diff --git a/src/main/java/org/bukkit/conversations/Conversable.java b/src/main/java/org/bukkit/conversations/Conversable.java new file mode 100644 index 00000000..d1e5c7dd --- /dev/null +++ b/src/main/java/org/bukkit/conversations/Conversable.java @@ -0,0 +1,55 @@ +package org.bukkit.conversations; + +/** + * The Conversable interface is used to indicate objects that can have + * conversations. + */ +public interface Conversable { + + /** + * Tests to see of a Conversable object is actively engaged in a + * conversation. + * + * @return True if a conversation is in progress + */ + public boolean isConversing(); + + /** + * Accepts input into the active conversation. If no conversation is in + * progress, this method does nothing. + * + * @param input The input message into the conversation + */ + public void acceptConversationInput(String input); + + /** + * Enters into a dialog with a Conversation object. + * + * @param conversation The conversation to begin + * @return True if the conversation should proceed, false if it has been + * enqueued + */ + public boolean beginConversation(Conversation conversation); + + /** + * Abandons an active conversation. + * + * @param conversation The conversation to abandon + */ + public void abandonConversation(Conversation conversation); + + /** + * Abandons an active conversation. + * + * @param conversation The conversation to abandon + * @param details Details about why the conversation was abandoned + */ + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details); + + /** + * Sends this sender a message raw + * + * @param message Message to be displayed + */ + public void sendRawMessage(String message); +} diff --git a/src/main/java/org/bukkit/conversations/Conversation.java b/src/main/java/org/bukkit/conversations/Conversation.java new file mode 100644 index 00000000..6d53ea2e --- /dev/null +++ b/src/main/java/org/bukkit/conversations/Conversation.java @@ -0,0 +1,297 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The Conversation class is responsible for tracking the current state of a + * conversation, displaying prompts to the user, and dispatching the user's + * response to the appropriate place. Conversation objects are not typically + * instantiated directly. Instead a {@link ConversationFactory} is used to + * construct identical conversations on demand. + *

+ * Conversation flow consists of a directed graph of {@link Prompt} objects. + * Each time a prompt gets input from the user, it must return the next prompt + * in the graph. Since each Prompt chooses the next Prompt, complex + * conversation trees can be implemented where the nature of the player's + * response directs the flow of the conversation. + *

+ * Each conversation has a {@link ConversationPrefix} that prepends all output + * from the conversation to the player. The ConversationPrefix can be used to + * display the plugin name or conversation status as the conversation evolves. + *

+ * Each conversation has a timeout measured in the number of inactive seconds + * to wait before abandoning the conversation. If the inactivity timeout is + * reached, the conversation is abandoned and the user's incoming and outgoing + * chat is returned to normal. + *

+ * You should not construct a conversation manually. Instead, use the {@link + * ConversationFactory} for access to all available options. + */ +public class Conversation { + + private Prompt firstPrompt; + private boolean abandoned; + protected Prompt currentPrompt; + protected ConversationContext context; + protected boolean modal; + protected boolean localEchoEnabled; + protected ConversationPrefix prefix; + protected List cancellers; + protected List abandonedListeners; + + /** + * Initializes a new Conversation. + * + * @param plugin The plugin that owns this conversation. + * @param forWhom The entity for whom this conversation is mediating. + * @param firstPrompt The first prompt in the conversation graph. + */ + public Conversation(Plugin plugin, Conversable forWhom, Prompt firstPrompt) { + this(plugin, forWhom, firstPrompt, new HashMap()); + } + + /** + * Initializes a new Conversation. + * + * @param plugin The plugin that owns this conversation. + * @param forWhom The entity for whom this conversation is mediating. + * @param firstPrompt The first prompt in the conversation graph. + * @param initialSessionData Any initial values to put in the conversation + * context sessionData map. + */ + public Conversation(Plugin plugin, Conversable forWhom, Prompt firstPrompt, Map initialSessionData) { + this.firstPrompt = firstPrompt; + this.context = new ConversationContext(plugin, forWhom, initialSessionData); + this.modal = true; + this.localEchoEnabled = true; + this.prefix = new NullConversationPrefix(); + this.cancellers = new ArrayList(); + this.abandonedListeners = new ArrayList(); + } + + /** + * Gets the entity for whom this conversation is mediating. + * + * @return The entity. + */ + public Conversable getForWhom() { + return context.getForWhom(); + } + + /** + * Gets the modality of this conversation. If a conversation is modal, all + * messages directed to the player are suppressed for the duration of the + * conversation. + * + * @return The conversation modality. + */ + public boolean isModal() { + return modal; + } + + /** + * Sets the modality of this conversation. If a conversation is modal, + * all messages directed to the player are suppressed for the duration of + * the conversation. + * + * @param modal The new conversation modality. + */ + void setModal(boolean modal) { + this.modal = modal; + } + + /** + * Gets the status of local echo for this conversation. If local echo is + * enabled, any text submitted to a conversation gets echoed back into the + * submitter's chat window. + * + * @return The status of local echo. + */ + public boolean isLocalEchoEnabled() { + return localEchoEnabled; + } + + /** + * Sets the status of local echo for this conversation. If local echo is + * enabled, any text submitted to a conversation gets echoed back into the + * submitter's chat window. + * + * @param localEchoEnabled The status of local echo. + */ + public void setLocalEchoEnabled(boolean localEchoEnabled) { + this.localEchoEnabled = localEchoEnabled; + } + + /** + * Gets the {@link ConversationPrefix} that prepends all output from this + * conversation. + * + * @return The ConversationPrefix in use. + */ + public ConversationPrefix getPrefix() { + return prefix; + } + + /** + * Sets the {@link ConversationPrefix} that prepends all output from this + * conversation. + * + * @param prefix The ConversationPrefix to use. + */ + void setPrefix(ConversationPrefix prefix) { + this.prefix = prefix; + } + + /** + * Adds a {@link ConversationCanceller} to the cancellers collection. + * + * @param canceller The {@link ConversationCanceller} to add. + */ + void addConversationCanceller(ConversationCanceller canceller) { + canceller.setConversation(this); + this.cancellers.add(canceller); + } + + /** + * Gets the list of {@link ConversationCanceller}s + * + * @return The list. + */ + public List getCancellers() { + return cancellers; + } + + /** + * Returns the Conversation's {@link ConversationContext}. + * + * @return The ConversationContext. + */ + public ConversationContext getContext() { + return context; + } + + /** + * Displays the first prompt of this conversation and begins redirecting + * the user's chat responses. + */ + public void begin() { + if (currentPrompt == null) { + abandoned = false; + currentPrompt = firstPrompt; + context.getForWhom().beginConversation(this); + } + } + + /** + * Returns Returns the current state of the conversation. + * + * @return The current state of the conversation. + */ + public ConversationState getState() { + if (currentPrompt != null) { + return ConversationState.STARTED; + } else if (abandoned) { + return ConversationState.ABANDONED; + } else { + return ConversationState.UNSTARTED; + } + } + + /** + * Passes player input into the current prompt. The next prompt (as + * determined by the current prompt) is then displayed to the user. + * + * @param input The user's chat text. + */ + public void acceptInput(String input) { + if (currentPrompt != null) { + + // Echo the user's input + if (localEchoEnabled) { + context.getForWhom().sendRawMessage(prefix.getPrefix(context) + input); + } + + // Test for conversation abandonment based on input + for (ConversationCanceller canceller : cancellers) { + if (canceller.cancelBasedOnInput(context, input)) { + abandon(new ConversationAbandonedEvent(this, canceller)); + return; + } + } + + // Not abandoned, output the next prompt + currentPrompt = currentPrompt.acceptInput(context, input); + outputNextPrompt(); + } + } + + /** + * Adds a {@link ConversationAbandonedListener}. + * + * @param listener The listener to add. + */ + public synchronized void addConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + } + + /** + * Removes a {@link ConversationAbandonedListener}. + * + * @param listener The listener to remove. + */ + public synchronized void removeConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.remove(listener); + } + + /** + * Abandons and resets the current conversation. Restores the user's + * normal chat behavior. + */ + public void abandon() { + abandon(new ConversationAbandonedEvent(this, new ManuallyAbandonedConversationCanceller())); + } + + /** + * Abandons and resets the current conversation. Restores the user's + * normal chat behavior. + * + * @param details Details about why the conversation was abandoned + */ + public synchronized void abandon(ConversationAbandonedEvent details) { + if (!abandoned) { + abandoned = true; + currentPrompt = null; + context.getForWhom().abandonConversation(this); + for (ConversationAbandonedListener listener : abandonedListeners) { + listener.conversationAbandoned(details); + } + } + } + + /** + * Displays the next user prompt and abandons the conversation if the next + * prompt is null. + */ + public void outputNextPrompt() { + if (currentPrompt == null) { + abandon(new ConversationAbandonedEvent(this)); + } else { + context.getForWhom().sendRawMessage(prefix.getPrefix(context) + currentPrompt.getPromptText(context)); + if (!currentPrompt.blocksForInput(context)) { + currentPrompt = currentPrompt.acceptInput(context, null); + outputNextPrompt(); + } + } + } + + public enum ConversationState { + UNSTARTED, + STARTED, + ABANDONED + } +} diff --git a/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java b/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java new file mode 100644 index 00000000..63c4a2aa --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.conversations; + +import java.util.EventObject; + +/** + * ConversationAbandonedEvent contains information about an abandoned + * conversation. + */ +public class ConversationAbandonedEvent extends EventObject { + + private ConversationContext context; + private ConversationCanceller canceller; + + public ConversationAbandonedEvent(Conversation conversation) { + this(conversation, null); + } + + public ConversationAbandonedEvent(Conversation conversation, ConversationCanceller canceller) { + super(conversation); + this.context = conversation.getContext(); + this.canceller = canceller; + } + + /** + * Gets the object that caused the conversation to be abandoned. + * + * @return The object that abandoned the conversation. + */ + public ConversationCanceller getCanceller() { + return canceller; + } + + /** + * Gets the abandoned conversation's conversation context. + * + * @return The abandoned conversation's conversation context. + */ + public ConversationContext getContext() { + return context; + } + + /** + * Indicates how the conversation was abandoned - naturally as part of the + * prompt chain or prematurely via a {@link ConversationCanceller}. + * + * @return True if the conversation is abandoned gracefully by a {@link + * Prompt} returning null or the next prompt. False of the + * conversations is abandoned prematurely by a ConversationCanceller. + */ + public boolean gracefulExit() { + return canceller == null; + } +} diff --git a/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java b/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java new file mode 100644 index 00000000..dc046b1f --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java @@ -0,0 +1,15 @@ +package org.bukkit.conversations; + +import java.util.EventListener; + +/** + */ +public interface ConversationAbandonedListener extends EventListener { + /** + * Called whenever a {@link Conversation} is abandoned. + * + * @param abandonedEvent Contains details about the abandoned + * conversation. + */ + public void conversationAbandoned(ConversationAbandonedEvent abandonedEvent); +} diff --git a/src/main/java/org/bukkit/conversations/ConversationCanceller.java b/src/main/java/org/bukkit/conversations/ConversationCanceller.java new file mode 100644 index 00000000..db43bb16 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationCanceller.java @@ -0,0 +1,34 @@ +package org.bukkit.conversations; + +/** + * A ConversationCanceller is a class that cancels an active {@link + * Conversation}. A Conversation can have more than one ConversationCanceller. + */ +public interface ConversationCanceller extends Cloneable { + + /** + * Sets the conversation this ConversationCanceller can optionally cancel. + * + * @param conversation A conversation. + */ + public void setConversation(Conversation conversation); + + /** + * Cancels a conversation based on user input. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return True to cancel the conversation, False otherwise. + */ + public boolean cancelBasedOnInput(ConversationContext context, String input); + + /** + * Allows the {@link ConversationFactory} to duplicate this + * ConversationCanceller when creating a new {@link Conversation}. + *

+ * Implementing this method should reset any internal object state. + * + * @return A clone. + */ + public ConversationCanceller clone(); +} diff --git a/src/main/java/org/bukkit/conversations/ConversationContext.java b/src/main/java/org/bukkit/conversations/ConversationContext.java new file mode 100644 index 00000000..7390a771 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationContext.java @@ -0,0 +1,79 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; + +import java.util.Map; + +/** + * A ConversationContext provides continuity between nodes in the prompt graph + * by giving the developer access to the subject of the conversation and a + * generic map for storing values that are shared between all {@link Prompt} + * invocations. + */ +public class ConversationContext { + private Conversable forWhom; + private Map sessionData; + private Plugin plugin; + + /** + * @param plugin The owning plugin. + * @param forWhom The subject of the conversation. + * @param initialSessionData Any initial values to put in the sessionData + * map. + */ + public ConversationContext(Plugin plugin, Conversable forWhom, Map initialSessionData) { + this.plugin = plugin; + this.forWhom = forWhom; + this.sessionData = initialSessionData; + } + + /** + * Gets the plugin that owns this conversation. + * + * @return The owning plugin. + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Gets the subject of the conversation. + * + * @return The subject of the conversation. + */ + public Conversable getForWhom() { + return forWhom; + } + + /** + * Gets the entire sessionData map. + * @return The full sessionData map. + */ + public Map getAllSessionData() { + return sessionData; + } + + /** + * Gets session data shared between all {@link Prompt} invocations. Use + * this as a way to pass data through each Prompt as the conversation + * develops. + * + * @param key The session data key. + * @return The requested session data. + */ + public Object getSessionData(Object key) { + return sessionData.get(key); + } + + /** + * Sets session data shared between all {@link Prompt} invocations. Use + * this as a way to pass data through each prompt as the conversation + * develops. + * + * @param key The session data key. + * @param value The session data value. + */ + public void setSessionData(Object key, Object value) { + sessionData.put(key, value); + } +} diff --git a/src/main/java/org/bukkit/conversations/ConversationFactory.java b/src/main/java/org/bukkit/conversations/ConversationFactory.java new file mode 100644 index 00000000..8a3b3920 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationFactory.java @@ -0,0 +1,225 @@ +package org.bukkit.conversations; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A ConversationFactory is responsible for creating a {@link Conversation} + * from a predefined template. A ConversationFactory is typically created when + * a plugin is instantiated and builds a Conversation each time a user + * initiates a conversation with the plugin. Each Conversation maintains its + * own state and calls back as needed into the plugin. + *

+ * The ConversationFactory implements a fluid API, allowing parameters to be + * set as an extension to the constructor. + */ +public class ConversationFactory { + + protected Plugin plugin; + protected boolean isModal; + protected boolean localEchoEnabled; + protected ConversationPrefix prefix; + protected Prompt firstPrompt; + protected Map initialSessionData; + protected String playerOnlyMessage; + protected List cancellers; + protected List abandonedListeners; + + /** + * Constructs a ConversationFactory. + * + * @param plugin The plugin that owns the factory. + */ + public ConversationFactory(Plugin plugin) { + this.plugin = plugin; + isModal = true; + localEchoEnabled = true; + prefix = new NullConversationPrefix(); + firstPrompt = Prompt.END_OF_CONVERSATION; + initialSessionData = new HashMap(); + playerOnlyMessage = null; + cancellers = new ArrayList(); + abandonedListeners = new ArrayList(); + } + + /** + * Sets the modality of all {@link Conversation}s created by this factory. + * If a conversation is modal, all messages directed to the player are + * suppressed for the duration of the conversation. + *

+ * The default is True. + * + * @param modal The modality of all conversations to be created. + * @return This object. + */ + public ConversationFactory withModality(boolean modal) { + isModal = modal; + return this; + } + + /** + * Sets the local echo status for all {@link Conversation}s created by + * this factory. If local echo is enabled, any text submitted to a + * conversation gets echoed back into the submitter's chat window. + * + * @param localEchoEnabled The status of local echo. + * @return This object. + */ + public ConversationFactory withLocalEcho(boolean localEchoEnabled) { + this.localEchoEnabled = localEchoEnabled; + return this; + } + + /** + * Sets the {@link ConversationPrefix} that prepends all output from all + * generated conversations. + *

+ * The default is a {@link NullConversationPrefix}; + * + * @param prefix The ConversationPrefix to use. + * @return This object. + */ + public ConversationFactory withPrefix(ConversationPrefix prefix) { + this.prefix = prefix; + return this; + } + + /** + * Sets the number of inactive seconds to wait before automatically + * abandoning all generated conversations. + *

+ * The default is 600 seconds (5 minutes). + * + * @param timeoutSeconds The number of seconds to wait. + * @return This object. + */ + public ConversationFactory withTimeout(int timeoutSeconds) { + return withConversationCanceller(new InactivityConversationCanceller(plugin, timeoutSeconds)); + } + + /** + * Sets the first prompt to use in all generated conversations. + *

+ * The default is Prompt.END_OF_CONVERSATION. + * + * @param firstPrompt The first prompt. + * @return This object. + */ + public ConversationFactory withFirstPrompt(Prompt firstPrompt) { + this.firstPrompt = firstPrompt; + return this; + } + + /** + * Sets any initial data with which to populate the conversation context + * sessionData map. + * + * @param initialSessionData The conversation context's initial + * sessionData. + * @return This object. + */ + public ConversationFactory withInitialSessionData(Map initialSessionData) { + this.initialSessionData = initialSessionData; + return this; + } + + /** + * Sets the player input that, when received, will immediately terminate + * the conversation. + * + * @param escapeSequence Input to terminate the conversation. + * @return This object. + */ + public ConversationFactory withEscapeSequence(String escapeSequence) { + return withConversationCanceller(new ExactMatchConversationCanceller(escapeSequence)); + } + + /** + * Adds a {@link ConversationCanceller} to constructed conversations. + * + * @param canceller The {@link ConversationCanceller} to add. + * @return This object. + */ + public ConversationFactory withConversationCanceller(ConversationCanceller canceller) { + cancellers.add(canceller); + return this; + } + + /** + * Prevents this factory from creating a conversation for non-player + * {@link Conversable} objects. + * + * @param playerOnlyMessage The message to return to a non-play in lieu of + * starting a conversation. + * @return This object. + */ + public ConversationFactory thatExcludesNonPlayersWithMessage(String playerOnlyMessage) { + this.playerOnlyMessage = playerOnlyMessage; + return this; + } + + /** + * Adds a {@link ConversationAbandonedListener} to all conversations + * constructed by this factory. + * + * @param listener The listener to add. + * @return This object. + */ + public ConversationFactory addConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + return this; + } + + /** + * Constructs a {@link Conversation} in accordance with the defaults set + * for this factory. + * + * @param forWhom The entity for whom the new conversation is mediating. + * @return A new conversation. + */ + public Conversation buildConversation(Conversable forWhom) { + //Abort conversation construction if we aren't supposed to talk to non-players + if (playerOnlyMessage != null && !(forWhom instanceof Player)) { + return new Conversation(plugin, forWhom, new NotPlayerMessagePrompt()); + } + + //Clone any initial session data + Map copiedInitialSessionData = new HashMap(); + copiedInitialSessionData.putAll(initialSessionData); + + //Build and return a conversation + Conversation conversation = new Conversation(plugin, forWhom, firstPrompt, copiedInitialSessionData); + conversation.setModal(isModal); + conversation.setLocalEchoEnabled(localEchoEnabled); + conversation.setPrefix(prefix); + + //Clone the conversation cancellers + for (ConversationCanceller canceller : cancellers) { + conversation.addConversationCanceller(canceller.clone()); + } + + //Add the ConversationAbandonedListeners + for (ConversationAbandonedListener listener : abandonedListeners) { + conversation.addConversationAbandonedListener(listener); + } + + return conversation; + } + + private class NotPlayerMessagePrompt extends MessagePrompt { + + public String getPromptText(ConversationContext context) { + return playerOnlyMessage; + } + + @Override + protected Prompt getNextPrompt(ConversationContext context) { + return Prompt.END_OF_CONVERSATION; + } + } +} diff --git a/src/main/java/org/bukkit/conversations/ConversationPrefix.java b/src/main/java/org/bukkit/conversations/ConversationPrefix.java new file mode 100644 index 00000000..3febfa6a --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationPrefix.java @@ -0,0 +1,17 @@ +package org.bukkit.conversations; + +/** + * A ConversationPrefix implementation prepends all output from the + * conversation to the player. The ConversationPrefix can be used to display + * the plugin name or conversation status as the conversation evolves. + */ +public interface ConversationPrefix { + + /** + * Gets the prefix to use before each message to the player. + * + * @param context Context information about the conversation. + * @return The prefix text. + */ + String getPrefix(ConversationContext context); +} diff --git a/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java b/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java new file mode 100644 index 00000000..327b9d99 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java @@ -0,0 +1,29 @@ +package org.bukkit.conversations; + +/** + * An ExactMatchConversationCanceller cancels a conversation if the user + * enters an exact input string + */ +public class ExactMatchConversationCanceller implements ConversationCanceller { + private String escapeSequence; + + /** + * Builds an ExactMatchConversationCanceller. + * + * @param escapeSequence The string that, if entered by the user, will + * cancel the conversation. + */ + public ExactMatchConversationCanceller(String escapeSequence) { + this.escapeSequence = escapeSequence; + } + + public void setConversation(Conversation conversation) {} + + public boolean cancelBasedOnInput(ConversationContext context, String input) { + return input.equals(escapeSequence); + } + + public ConversationCanceller clone() { + return new ExactMatchConversationCanceller(escapeSequence); + } +} diff --git a/src/main/java/org/bukkit/conversations/FixedSetPrompt.java b/src/main/java/org/bukkit/conversations/FixedSetPrompt.java new file mode 100644 index 00000000..bf55447a --- /dev/null +++ b/src/main/java/org/bukkit/conversations/FixedSetPrompt.java @@ -0,0 +1,46 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; + +/** + * FixedSetPrompt is the base class for any prompt that requires a fixed set + * response from the user. + */ +public abstract class FixedSetPrompt extends ValidatingPrompt { + + protected List fixedSet; + + /** + * Creates a FixedSetPrompt from a set of strings. + *

+ * foo = new FixedSetPrompt("bar", "cheese", "panda"); + * + * @param fixedSet A fixed set of strings, one of which the user must + * type. + */ + public FixedSetPrompt(String... fixedSet) { + super(); + this.fixedSet = Arrays.asList(fixedSet); + } + + private FixedSetPrompt() {} + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + return fixedSet.contains(input); + } + + /** + * Utility function to create a formatted string containing all the + * options declared in the constructor. + * + * @return the options formatted like "[bar, cheese, panda]" if bar, + * cheese, and panda were the options used + */ + protected String formatFixedSet() { + return "[" + StringUtils.join(fixedSet, ", ") + "]"; + } +} diff --git a/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java b/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java new file mode 100644 index 00000000..617e26d8 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java @@ -0,0 +1,78 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; + +/** + * An InactivityConversationCanceller will cancel a {@link Conversation} after + * a period of inactivity by the user. + */ +public class InactivityConversationCanceller implements ConversationCanceller { + protected Plugin plugin; + protected int timeoutSeconds; + protected Conversation conversation; + private int taskId = -1; + + /** + * Creates an InactivityConversationCanceller. + * + * @param plugin The owning plugin. + * @param timeoutSeconds The number of seconds of inactivity to wait. + */ + public InactivityConversationCanceller(Plugin plugin, int timeoutSeconds) { + this.plugin = plugin; + this.timeoutSeconds = timeoutSeconds; + } + + public void setConversation(Conversation conversation) { + this.conversation = conversation; + startTimer(); + } + + public boolean cancelBasedOnInput(ConversationContext context, String input) { + // Reset the inactivity timer + stopTimer(); + startTimer(); + return false; + } + + public ConversationCanceller clone() { + return new InactivityConversationCanceller(plugin, timeoutSeconds); + } + + /** + * Starts an inactivity timer. + */ + private void startTimer() { + taskId = plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + public void run() { + if (conversation.getState() == Conversation.ConversationState.UNSTARTED) { + startTimer(); + } else if (conversation.getState() == Conversation.ConversationState.STARTED) { + cancelling(conversation); + conversation.abandon(new ConversationAbandonedEvent(conversation, InactivityConversationCanceller.this)); + } + } + }, timeoutSeconds * 20); + } + + /** + * Stops the active inactivity timer. + */ + private void stopTimer() { + if (taskId != -1) { + plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + + /** + * Subclasses of InactivityConversationCanceller can override this method + * to take additional actions when the inactivity timer abandons the + * conversation. + * + * @param conversation The conversation being abandoned. + */ + protected void cancelling(Conversation conversation) { + + } +} diff --git a/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java b/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java new file mode 100644 index 00000000..1272f22c --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java @@ -0,0 +1,20 @@ +package org.bukkit.conversations; + +/** + * The ManuallyAbandonedConversationCanceller is only used as part of a {@link + * ConversationAbandonedEvent} to indicate that the conversation was manually + * abandoned by programmatically calling the abandon() method on it. + */ +public class ManuallyAbandonedConversationCanceller implements ConversationCanceller { + public void setConversation(Conversation conversation) { + throw new UnsupportedOperationException(); + } + + public boolean cancelBasedOnInput(ConversationContext context, String input) { + throw new UnsupportedOperationException(); + } + + public ConversationCanceller clone() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/bukkit/conversations/MessagePrompt.java b/src/main/java/org/bukkit/conversations/MessagePrompt.java new file mode 100644 index 00000000..6d379bec --- /dev/null +++ b/src/main/java/org/bukkit/conversations/MessagePrompt.java @@ -0,0 +1,42 @@ +package org.bukkit.conversations; + +/** + * MessagePrompt is the base class for any prompt that only displays a message + * to the user and requires no input. + */ +public abstract class MessagePrompt implements Prompt { + + public MessagePrompt() { + super(); + } + + /** + * Message prompts never wait for user input before continuing. + * + * @param context Context information about the conversation. + * @return Always false. + */ + public boolean blocksForInput(ConversationContext context) { + return false; + } + + /** + * Accepts and ignores any user input, returning the next prompt in the + * prompt graph instead. + * + * @param context Context information about the conversation. + * @param input Ignored. + * @return The next prompt in the prompt graph. + */ + public Prompt acceptInput(ConversationContext context, String input) { + return getNextPrompt(context); + } + + /** + * Override this method to return the next prompt in the prompt graph. + * + * @param context Context information about the conversation. + * @return The next prompt in the prompt graph. + */ + protected abstract Prompt getNextPrompt(ConversationContext context); +} diff --git a/src/main/java/org/bukkit/conversations/NullConversationPrefix.java b/src/main/java/org/bukkit/conversations/NullConversationPrefix.java new file mode 100644 index 00000000..57d777e7 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/NullConversationPrefix.java @@ -0,0 +1,18 @@ +package org.bukkit.conversations; + +/** + * NullConversationPrefix is a {@link ConversationPrefix} implementation that + * displays nothing in front of conversation output. + */ +public class NullConversationPrefix implements ConversationPrefix { + + /** + * Prepends each conversation message with an empty string. + * + * @param context Context information about the conversation. + * @return An empty string. + */ + public String getPrefix(ConversationContext context) { + return ""; + } +} diff --git a/src/main/java/org/bukkit/conversations/NumericPrompt.java b/src/main/java/org/bukkit/conversations/NumericPrompt.java new file mode 100644 index 00000000..996c81e4 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/NumericPrompt.java @@ -0,0 +1,82 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang3.math.NumberUtils; + +/** + * NumericPrompt is the base class for any prompt that requires a {@link + * Number} response from the user. + */ +public abstract class NumericPrompt extends ValidatingPrompt { + public NumericPrompt() { + super(); + } + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + return NumberUtils.isNumber(input) && isNumberValid(context, NumberUtils.createNumber(input)); + } + + /** + * Override this method to do further validation on the numeric player + * input after the input has been determined to actually be a number. + * + * @param context Context information about the conversation. + * @param input The number the player provided. + * @return The validity of the player's input. + */ + protected boolean isNumberValid(ConversationContext context, Number input) { + return true; + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + try { + return acceptValidatedInput(context, NumberUtils.createNumber(input)); + } catch (NumberFormatException e) { + return acceptValidatedInput(context, NumberUtils.INTEGER_ZERO); + } + } + + /** + * Override this method to perform some action with the user's integer + * response. + * + * @param context Context information about the conversation. + * @param input The user's response as a {@link Number}. + * @return The next {@link Prompt} in the prompt graph. + */ + protected abstract Prompt acceptValidatedInput(ConversationContext context, Number input); + + @Override + protected String getFailedValidationText(ConversationContext context, String invalidInput) { + if (NumberUtils.isNumber(invalidInput)) { + return getFailedValidationText(context, NumberUtils.createNumber(invalidInput)); + } else { + return getInputNotNumericText(context, invalidInput); + } + } + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid number. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + protected String getInputNotNumericText(ConversationContext context, String invalidInput) { + return null; + } + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid numeric input. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + protected String getFailedValidationText(ConversationContext context, Number invalidInput) { + return null; + } +} diff --git a/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java b/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java new file mode 100644 index 00000000..dcf8090d --- /dev/null +++ b/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java @@ -0,0 +1,37 @@ +package org.bukkit.conversations; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * PlayerNamePrompt is the base class for any prompt that requires the player + * to enter another player's name. + */ +public abstract class PlayerNamePrompt extends ValidatingPrompt { + private Plugin plugin; + + public PlayerNamePrompt(Plugin plugin) { + super(); + this.plugin = plugin; + } + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + return plugin.getServer().getPlayer(input) != null; + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + return acceptValidatedInput(context, plugin.getServer().getPlayer(input)); + } + + /** + * Override this method to perform some action with the user's player name + * response. + * + * @param context Context information about the conversation. + * @param input The user's player name response. + * @return The next {@link Prompt} in the prompt graph. + */ + protected abstract Prompt acceptValidatedInput(ConversationContext context, Player input); +} diff --git a/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java b/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java new file mode 100644 index 00000000..dcb7e24a --- /dev/null +++ b/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java @@ -0,0 +1,39 @@ +package org.bukkit.conversations; + +import org.bukkit.ChatColor; +import org.bukkit.plugin.Plugin; + +/** + * PluginNameConversationPrefix is a {@link ConversationPrefix} implementation + * that displays the plugin name in front of conversation output. + */ +public class PluginNameConversationPrefix implements ConversationPrefix { + + protected String separator; + protected ChatColor prefixColor; + protected Plugin plugin; + + private String cachedPrefix; + + public PluginNameConversationPrefix(Plugin plugin) { + this(plugin, " > ", ChatColor.LIGHT_PURPLE); + } + + public PluginNameConversationPrefix(Plugin plugin, String separator, ChatColor prefixColor) { + this.separator = separator; + this.prefixColor = prefixColor; + this.plugin = plugin; + + cachedPrefix = prefixColor + plugin.getDescription().getName() + separator + ChatColor.WHITE; + } + + /** + * Prepends each conversation message with the plugin name. + * + * @param context Context information about the conversation. + * @return An empty string. + */ + public String getPrefix(ConversationContext context) { + return cachedPrefix; + } +} diff --git a/src/main/java/org/bukkit/conversations/Prompt.java b/src/main/java/org/bukkit/conversations/Prompt.java new file mode 100644 index 00000000..7519c843 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/Prompt.java @@ -0,0 +1,45 @@ +package org.bukkit.conversations; + +/** + * A Prompt is the main constituent of a {@link Conversation}. Each prompt + * displays text to the user and optionally waits for a user's response. + * Prompts are chained together into a directed graph that represents the + * conversation flow. To halt a conversation, END_OF_CONVERSATION is returned + * in liu of another Prompt object. + */ +public interface Prompt extends Cloneable { + + /** + * A convenience constant for indicating the end of a conversation. + */ + static final Prompt END_OF_CONVERSATION = null; + + /** + * Gets the text to display to the user when this prompt is first + * presented. + * + * @param context Context information about the conversation. + * @return The text to display. + */ + String getPromptText(ConversationContext context); + + /** + * Checks to see if this prompt implementation should wait for user input + * or immediately display the next prompt. + * + * @param context Context information about the conversation. + * @return If true, the {@link Conversation} will wait for input before + * continuing. + */ + boolean blocksForInput(ConversationContext context); + + /** + * Accepts and processes input from the user. Using the input, the next + * Prompt in the prompt graph is returned. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return The next Prompt in the prompt graph. + */ + Prompt acceptInput(ConversationContext context, String input); +} diff --git a/src/main/java/org/bukkit/conversations/RegexPrompt.java b/src/main/java/org/bukkit/conversations/RegexPrompt.java new file mode 100644 index 00000000..a3c7d1f1 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/RegexPrompt.java @@ -0,0 +1,28 @@ +package org.bukkit.conversations; + +import java.util.regex.Pattern; + +/** + * RegexPrompt is the base class for any prompt that requires an input + * validated by a regular expression. + */ +public abstract class RegexPrompt extends ValidatingPrompt { + + private Pattern pattern; + + public RegexPrompt(String regex) { + this(Pattern.compile(regex)); + } + + public RegexPrompt(Pattern pattern) { + super(); + this.pattern = pattern; + } + + private RegexPrompt() {} + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + return pattern.matcher(input).matches(); + } +} diff --git a/src/main/java/org/bukkit/conversations/StringPrompt.java b/src/main/java/org/bukkit/conversations/StringPrompt.java new file mode 100644 index 00000000..3e3e5e9e --- /dev/null +++ b/src/main/java/org/bukkit/conversations/StringPrompt.java @@ -0,0 +1,18 @@ +package org.bukkit.conversations; + +/** + * StringPrompt is the base class for any prompt that accepts an arbitrary + * string from the user. + */ +public abstract class StringPrompt implements Prompt { + + /** + * Ensures that the prompt waits for the user to provide input. + * + * @param context Context information about the conversation. + * @return True. + */ + public boolean blocksForInput(ConversationContext context) { + return true; + } +} diff --git a/src/main/java/org/bukkit/conversations/ValidatingPrompt.java b/src/main/java/org/bukkit/conversations/ValidatingPrompt.java new file mode 100644 index 00000000..f41adb4a --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ValidatingPrompt.java @@ -0,0 +1,78 @@ +package org.bukkit.conversations; + +import org.bukkit.ChatColor; + +/** + * ValidatingPrompt is the base class for any prompt that requires validation. + * ValidatingPrompt will keep replaying the prompt text until the user enters + * a valid response. + */ +public abstract class ValidatingPrompt implements Prompt { + public ValidatingPrompt() { + super(); + } + + /** + * Accepts and processes input from the user and validates it. If + * validation fails, this prompt is returned for re-execution, otherwise + * the next Prompt in the prompt graph is returned. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return This prompt or the next Prompt in the prompt graph. + */ + public Prompt acceptInput(ConversationContext context, String input) { + if (isInputValid(context, input)) { + return acceptValidatedInput(context, input); + } else { + String failPrompt = getFailedValidationText(context, input); + if (failPrompt != null) { + context.getForWhom().sendRawMessage(ChatColor.RED + failPrompt); + } + // Redisplay this prompt to the user to re-collect input + return this; + } + } + + /** + * Ensures that the prompt waits for the user to provide input. + * + * @param context Context information about the conversation. + * @return True. + */ + public boolean blocksForInput(ConversationContext context) { + return true; + } + + /** + * Override this method to check the validity of the player's input. + * + * @param context Context information about the conversation. + * @param input The player's raw console input. + * @return True or false depending on the validity of the input. + */ + protected abstract boolean isInputValid(ConversationContext context, String input); + + /** + * Override this method to accept and processes the validated input from + * the user. Using the input, the next Prompt in the prompt graph should + * be returned. + * + * @param context Context information about the conversation. + * @param input The validated input text from the user. + * @return The next Prompt in the prompt graph. + */ + protected abstract Prompt acceptValidatedInput(ConversationContext context, String input); + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid input. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + protected String getFailedValidationText(ConversationContext context, String invalidInput) { + return null; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftArt.java b/src/main/java/org/bukkit/craftbukkit/CraftArt.java new file mode 100644 index 00000000..cea66129 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftArt.java @@ -0,0 +1,74 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.entity.item.EntityPainting.EnumArt; +import org.bukkit.Art; + +// Safety class, will break if either side changes +public class CraftArt { + + public static Art NotchToBukkit(EnumArt art) { + switch (art) { + case KEBAB: return Art.KEBAB; + case AZTEC: return Art.AZTEC; + case ALBAN: return Art.ALBAN; + case AZTEC_2: return Art.AZTEC2; + case BOMB: return Art.BOMB; + case PLANT: return Art.PLANT; + case WASTELAND: return Art.WASTELAND; + case POOL: return Art.POOL; + case COURBET: return Art.COURBET; + case SEA: return Art.SEA; + case SUNSET: return Art.SUNSET; + case CREEBET: return Art.CREEBET; + case WANDERER: return Art.WANDERER; + case GRAHAM: return Art.GRAHAM; + case MATCH: return Art.MATCH; + case BUST: return Art.BUST; + case STAGE: return Art.STAGE; + case VOID: return Art.VOID; + case SKULL_AND_ROSES: return Art.SKULL_AND_ROSES; + case FIGHTERS: return Art.FIGHTERS; + case POINTER: return Art.POINTER; + case PIGSCENE: return Art.PIGSCENE; + case BURNING_SKULL: return Art.BURNINGSKULL; + case SKELETON: return Art.SKELETON; + case DONKEY_KONG: return Art.DONKEYKONG; + case WITHER: return Art.WITHER; + default: + throw new AssertionError(art); + } + } + + public static EnumArt BukkitToNotch(Art art) { + switch (art) { + case KEBAB: return EnumArt.KEBAB; + case AZTEC: return EnumArt.AZTEC; + case ALBAN: return EnumArt.ALBAN; + case AZTEC2: return EnumArt.AZTEC_2; + case BOMB: return EnumArt.BOMB; + case PLANT: return EnumArt.PLANT; + case WASTELAND: return EnumArt.WASTELAND; + case POOL: return EnumArt.POOL; + case COURBET: return EnumArt.COURBET; + case SEA: return EnumArt.SEA; + case SUNSET: return EnumArt.SUNSET; + case CREEBET: return EnumArt.CREEBET; + case WANDERER: return EnumArt.WANDERER; + case GRAHAM: return EnumArt.GRAHAM; + case MATCH: return EnumArt.MATCH; + case BUST: return EnumArt.BUST; + case STAGE: return EnumArt.STAGE; + case VOID: return EnumArt.VOID; + case SKULL_AND_ROSES: return EnumArt.SKULL_AND_ROSES; + case FIGHTERS: return EnumArt.FIGHTERS; + case POINTER: return EnumArt.POINTER; + case PIGSCENE: return EnumArt.PIGSCENE; + case BURNINGSKULL: return EnumArt.BURNING_SKULL; + case SKELETON: return EnumArt.SKELETON; + case DONKEYKONG: return EnumArt.DONKEY_KONG; + case WITHER: return EnumArt.WITHER; + default: + throw new AssertionError(art); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java new file mode 100644 index 00000000..bf89868b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -0,0 +1,315 @@ +package org.bukkit.craftbukkit; + +import java.lang.ref.WeakReference; +import java.util.Arrays; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeProvider; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.entity.Entity; +import org.bukkit.ChunkSnapshot; + +public class CraftChunk implements Chunk { + private WeakReference weakChunk; + private final WorldServer worldServer; + private final int x; + private final int z; + private static final byte[] emptyData = new byte[2048]; + private static final short[] emptyBlockIDs = new short[4096]; + private static final byte[] emptySkyLight = new byte[2048]; + + public CraftChunk(net.minecraft.world.chunk.Chunk chunk) { + this.weakChunk = new WeakReference(chunk); + + worldServer = (WorldServer) getHandle().getWorld(); + x = getHandle().x; + z = getHandle().z; + } + + public World getWorld() { + return worldServer.getWorld(); + } + + public CraftWorld getCraftWorld() { + return (CraftWorld) getWorld(); + } + + public net.minecraft.world.chunk.Chunk getHandle() { + net.minecraft.world.chunk.Chunk c = weakChunk.get(); + + if (c == null) { + c = worldServer.getChunkFromChunkCoords(x, z); + + weakChunk = new WeakReference<>(c); + } + + return c; + } + + void breakLink() { + weakChunk.clear(); + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } + + @Override + public String toString() { + return "CraftChunk{" + "x=" + getX() + "z=" + getZ() + '}'; + } + + public Block getBlock(int x, int y, int z) { + return new CraftBlock(this, (getX() << 4) | (x & 0xF), y, (getZ() << 4) | (z & 0xF)); + } + + public Entity[] getEntities() { + int count = 0, index = 0; + net.minecraft.world.chunk.Chunk chunk = getHandle(); + + for (int i = 0; i < 16; i++) { + count += chunk.getEntityLists()[i].size(); + } + + Entity[] entities = new Entity[count]; + + for (int i = 0; i < 16; i++) { + + for (Object obj : chunk.getEntityLists()[i].toArray()) { + if (!(obj instanceof net.minecraft.entity.Entity)) { + continue; + } + + entities[index++] = ((net.minecraft.entity.Entity) obj).getBukkitEntity(); + } + } + + return entities; + } + + public BlockState[] getTileEntities() { + int index = 0; + net.minecraft.world.chunk.Chunk chunk = getHandle(); + + BlockState[] entities = new BlockState[chunk.getTileEntityMap().size()]; + + for (Object obj : chunk.getTileEntityMap().keySet().toArray()) { + if (!(obj instanceof BlockPos)) { + continue; + } + + BlockPos position = (BlockPos) obj; + entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); + } + + return entities; + } + + public boolean isLoaded() { + return getWorld().isChunkLoaded(this); + } + + public boolean load() { + return getWorld().loadChunk(getX(), getZ(), true); + } + + public boolean load(boolean generate) { + return getWorld().loadChunk(getX(), getZ(), generate); + } + + public boolean unload() { + return getWorld().unloadChunk(getX(), getZ()); + } + + @Override + public boolean isSlimeChunk() { + // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk + return getHandle().getRandomWithSeed(987234911L).nextInt(10) == 0; + } + + public boolean unload(boolean save) { + return getWorld().unloadChunk(getX(), getZ(), save); + } + + public boolean unload(boolean save, boolean safe) { + return getWorld().unloadChunk(getX(), getZ(), save, safe); + } + + public ChunkSnapshot getChunkSnapshot() { + return getChunkSnapshot(true, false, false); + } + + public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { + net.minecraft.world.chunk.Chunk chunk = getHandle(); + + ExtendedBlockStorage[] cs = chunk.getBlockStorageArray(); + short[][] sectionBlockIDs = new short[cs.length][]; + byte[][] sectionBlockData = new byte[cs.length][]; + byte[][] sectionSkyLights = new byte[cs.length][]; + byte[][] sectionEmitLights = new byte[cs.length][]; + boolean[] sectionEmpty = new boolean[cs.length]; + + for (int i = 0; i < cs.length; i++) { + if (cs[i] == null) { // Section is empty? + sectionBlockIDs[i] = emptyBlockIDs; + sectionBlockData[i] = emptyData; + sectionSkyLights[i] = emptySkyLight; + sectionEmitLights[i] = emptyData; + sectionEmpty[i] = true; + } else { // Not empty + short[] blockids = new short[4096]; + + byte[] rawIds = new byte[4096]; + NibbleArray data = new NibbleArray(); + cs[i].getData().getDataForNBT(rawIds, data); + + byte[] dataValues = sectionBlockData[i] = data.getData(); + + // Copy base IDs + for (int j = 0; j < 4096; j++) { + blockids[j] = (short) (rawIds[j] & 0xFF); + } + + sectionBlockIDs[i] = blockids; + + if (cs[i].getSkyLight() == null) { + sectionSkyLights[i] = emptyData; + } else { + sectionSkyLights[i] = new byte[2048]; + System.arraycopy(cs[i].getSkyLight().getData(), 0, sectionSkyLights[i], 0, 2048); + } + sectionEmitLights[i] = new byte[2048]; + System.arraycopy(cs[i].getBlockLight().getData(), 0, sectionEmitLights[i], 0, 2048); + } + } + + int[] hmap = null; + + if (includeMaxBlockY) { + hmap = new int[256]; // Get copy of height map + System.arraycopy(chunk.getHeightMap(), 0, hmap, 0, 256); + } + + Biome[] biome = null; + double[] biomeTemp = null; + double[] biomeRain = null; + + if (includeBiome || includeBiomeTempRain) { + BiomeProvider wcm = chunk.getWorld().getBiomeProvider(); + + if (includeBiome) { + biome = new Biome[256]; + for (int i = 0; i < 256; i++) { + biome[i] = chunk.getBiome(new BlockPos(i & 0xF, 0, i >> 4), wcm); + } + } + + if (includeBiomeTempRain) { + biomeTemp = new double[256]; + biomeRain = new double[256]; + float[] dat = getTemperatures(wcm, getX() << 4, getZ() << 4); + + for (int i = 0; i < 256; i++) { + biomeTemp[i] = dat[i]; + } + + /* Removed 15w46a + dat = wcm.getWetness(null, getX() << 4, getZ() << 4, 16, 16); + + for (int i = 0; i < 256; i++) { + biomeRain[i] = dat[i]; + } + */ + } + } + + World world = getWorld(); + return new CraftChunkSnapshot(getX(), getZ(), world.getName(), world.getFullTime(), sectionBlockIDs, sectionBlockData, sectionSkyLights, sectionEmitLights, sectionEmpty, hmap, biome, biomeTemp, biomeRain); + } + + public static ChunkSnapshot getEmptyChunkSnapshot(int x, int z, CraftWorld world, boolean includeBiome, boolean includeBiomeTempRain) { + Biome[] biome = null; + double[] biomeTemp = null; + double[] biomeRain = null; + + if (includeBiome || includeBiomeTempRain) { + BiomeProvider wcm = world.getHandle().getBiomeProvider(); + + if (includeBiome) { + biome = new Biome[256]; + for (int i = 0; i < 256; i++) { + biome[i] = world.getHandle().getBiome(new BlockPos((x << 4) + (i & 0xF), 0, (z << 4) + (i >> 4))); + } + } + + if (includeBiomeTempRain) { + biomeTemp = new double[256]; + biomeRain = new double[256]; + float[] dat = getTemperatures(wcm, x << 4, z << 4); + + for (int i = 0; i < 256; i++) { + biomeTemp[i] = dat[i]; + } + + /* Removed 15w46a + dat = wcm.getWetness(null, x << 4, z << 4, 16, 16); + + for (int i = 0; i < 256; i++) { + biomeRain[i] = dat[i]; + } + */ + } + } + + /* Fill with empty data */ + int hSection = world.getMaxHeight() >> 4; + short[][] blockIDs = new short[hSection][]; + byte[][] skyLight = new byte[hSection][]; + byte[][] emitLight = new byte[hSection][]; + byte[][] blockData = new byte[hSection][]; + boolean[] empty = new boolean[hSection]; + + for (int i = 0; i < hSection; i++) { + blockIDs[i] = emptyBlockIDs; + skyLight[i] = emptySkyLight; + emitLight[i] = emptyData; + blockData[i] = emptyData; + empty[i] = true; + } + + return new CraftChunkSnapshot(x, z, world.getName(), world.getFullTime(), blockIDs, blockData, skyLight, emitLight, empty, new int[256], biome, biomeTemp, biomeRain); + } + + private static float[] getTemperatures(BiomeProvider chunkmanager, int chunkX, int chunkZ) { + Biome[] biomes = chunkmanager.getBiomes(null, chunkX, chunkZ, 16, 16); + float[] temps = new float[biomes.length]; + + for (int i = 0; i < biomes.length; i++) { + float temp = biomes[i].getDefaultTemperature(); // Vanilla of olde: ((int) biomes[i].temperature * 65536.0F) / 65536.0F + + if (temp > 1F) { + temp = 1F; + } + + temps[i] = temp; + } + + return temps; + } + + static { + Arrays.fill(emptySkyLight, (byte) 0xFF); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java new file mode 100644 index 00000000..79236484 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java @@ -0,0 +1,101 @@ +package org.bukkit.craftbukkit; + +import org.bukkit.ChunkSnapshot; +import org.bukkit.Material; +import net.minecraft.world.biome.Biome; +import org.bukkit.craftbukkit.block.CraftBlock; + +/** + * Represents a static, thread-safe snapshot of chunk of blocks + * Purpose is to allow clean, efficient copy of a chunk data to be made, and then handed off for processing in another thread (e.g. map rendering) + */ +public class CraftChunkSnapshot implements ChunkSnapshot { + private final int x, z; + private final String worldname; + private final short[][] blockids; /* Block IDs, by section */ + private final byte[][] blockdata; + private final byte[][] skylight; + private final byte[][] emitlight; + private final boolean[] empty; + private final int[] hmap; // Height map + private final long captureFulltime; + private final Biome[] biome; + private final double[] biomeTemp; + private final double[] biomeRain; + + CraftChunkSnapshot(int x, int z, String wname, long wtime, short[][] sectionBlockIDs, byte[][] sectionBlockData, byte[][] sectionSkyLights, byte[][] sectionEmitLights, boolean[] sectionEmpty, int[] hmap, Biome[] biome, double[] biomeTemp, double[] biomeRain) { + this.x = x; + this.z = z; + this.worldname = wname; + this.captureFulltime = wtime; + this.blockids = sectionBlockIDs; + this.blockdata = sectionBlockData; + this.skylight = sectionSkyLights; + this.emitlight = sectionEmitLights; + this.empty = sectionEmpty; + this.hmap = hmap; + this.biome = biome; + this.biomeTemp = biomeTemp; + this.biomeRain = biomeRain; + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } + + public String getWorldName() { + return worldname; + } + + @Override + public Material getBlockType(int x, int y, int z) { + return Material.getMaterial(getBlockTypeId(x, y, z)); + } + + public final int getBlockTypeId(int x, int y, int z) { + return blockids[y >> 4][((y & 0xF) << 8) | (z << 4) | x]; + } + + public final int getBlockData(int x, int y, int z) { + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); + return (blockdata[y >> 4][off] >> ((x & 1) << 2)) & 0xF; + } + + public final int getBlockSkyLight(int x, int y, int z) { + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); + return (skylight[y >> 4][off] >> ((x & 1) << 2)) & 0xF; + } + + public final int getBlockEmittedLight(int x, int y, int z) { + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); + return (emitlight[y >> 4][off] >> ((x & 1) << 2)) & 0xF; + } + + public final int getHighestBlockYAt(int x, int z) { + return hmap[z << 4 | x]; + } + + public final org.bukkit.block.Biome getBiome(int x, int z) { + return CraftBlock.biomeBaseToBiome(biome[z << 4 | x]); + } + + public final double getRawBiomeTemperature(int x, int z) { + return biomeTemp[z << 4 | x]; + } + + public final double getRawBiomeRainfall(int x, int z) { + return biomeRain[z << 4 | x]; + } + + public final long getCaptureFullTime() { + return captureFulltime; + } + + public final boolean isSectionEmpty(int sy) { + return empty[sy]; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java new file mode 100644 index 00000000..a39d02d6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java @@ -0,0 +1,42 @@ +package org.bukkit.craftbukkit; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Map; + +import net.minecraft.crash.ICrashReportDetail; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; + +import net.minecraft.server.MinecraftServer; + +public class CraftCrashReport implements ICrashReportDetail { + + public Object call() throws Exception { + StringWriter value = new StringWriter(); + try { + value.append("\n Running: ").append(Bukkit.getName()).append(" version ").append(Bukkit.getVersion()).append(" (Implementing API version ").append(Bukkit.getBukkitVersion()).append(") ").append(String.valueOf(MinecraftServer.getServerCB().isServerInOnlineMode())); + value.append("\n Plugins: {"); + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + PluginDescriptionFile description = plugin.getDescription(); + value.append(' ').append(description.getFullName()).append(' ').append(description.getMain()).append(' ').append(Arrays.toString(description.getAuthors().toArray())).append(','); + } + value.append("}\n Warnings: ").append(Bukkit.getWarningState().name()); + value.append("\n Reload Count: ").append(String.valueOf(MinecraftServer.getServerCB().server.reloadCount)); + value.append("\n Threads: {"); + for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) { + value.append(' ').append(entry.getKey().getState().name()).append(' ').append(entry.getKey().getName()).append(": ").append(Arrays.toString(entry.getValue())).append(','); + } + value.append("}\n ").append(Bukkit.getScheduler().toString()); + } catch (Throwable t) { + value.append("\n Failed to handle CraftCrashReport:\n"); + PrintWriter writer = new PrintWriter(value); + t.printStackTrace(writer); + writer.flush(); + } + return value.toString(); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java new file mode 100644 index 00000000..1576f9b1 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java @@ -0,0 +1,69 @@ +package org.bukkit.craftbukkit; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Effect; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.potion.Potion; + +public class CraftEffect { + public static int getDataValue(Effect effect, T data) { + int datavalue; + switch(effect) { + case VILLAGER_PLANT_GROW: + datavalue = (Integer) data; + break; + case POTION_BREAK: + datavalue = ((Potion) data).toDamageValue() & 0x3F; + break; + case RECORD_PLAY: + Validate.isTrue(((Material) data).isRecord(), "Invalid record type!"); + datavalue = ((Material) data).getId(); + break; + case SMOKE: + switch((BlockFace) data) { // TODO: Verify (Where did these values come from...?) + case SOUTH_EAST: + datavalue = 0; + break; + case SOUTH: + datavalue = 1; + break; + case SOUTH_WEST: + datavalue = 2; + break; + case EAST: + datavalue = 3; + break; + case UP: + case SELF: + datavalue = 4; + break; + case WEST: + datavalue = 5; + break; + case NORTH_EAST: + datavalue = 6; + break; + case NORTH: + datavalue = 7; + break; + case NORTH_WEST: + datavalue = 8; + break; + default: + throw new IllegalArgumentException("Bad smoke direction!"); + } + break; + case STEP_SOUND: + Validate.isTrue(((Material) data).isBlock(), "Material is not a block!"); + datavalue = ((Material) data).getId(); + break; + case ITEM_BREAK: + datavalue = ((Material) data).getId(); + break; + default: + datavalue = 0; + } + return datavalue; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java new file mode 100644 index 00000000..53e53bb8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.inventory.EntityEquipmentSlot; +import org.bukkit.inventory.EquipmentSlot; + +public class CraftEquipmentSlot { + + private static final EntityEquipmentSlot[] slots = new EntityEquipmentSlot[EquipmentSlot.values().length]; + private static final EquipmentSlot[] enums = new EquipmentSlot[EntityEquipmentSlot.values().length]; + + static { + set(EquipmentSlot.HAND, EntityEquipmentSlot.MAINHAND); + set(EquipmentSlot.OFF_HAND, EntityEquipmentSlot.OFFHAND); + set(EquipmentSlot.FEET, EntityEquipmentSlot.FEET); + set(EquipmentSlot.LEGS, EntityEquipmentSlot.LEGS); + set(EquipmentSlot.CHEST, EntityEquipmentSlot.CHEST); + set(EquipmentSlot.HEAD, EntityEquipmentSlot.HEAD); + } + + private static void set(EquipmentSlot type, EntityEquipmentSlot value) { + slots[type.ordinal()] = value; + enums[value.ordinal()] = type; + } + + public static EquipmentSlot getSlot(EntityEquipmentSlot nms) { + return enums[nms.ordinal()]; + } + + public static EntityEquipmentSlot getNMS(EquipmentSlot slot) { + return slots[slot.ordinal()]; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftIpBanEntry.java b/src/main/java/org/bukkit/craftbukkit/CraftIpBanEntry.java new file mode 100644 index 00000000..a35ffcb0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftIpBanEntry.java @@ -0,0 +1,87 @@ +package org.bukkit.craftbukkit; + +import java.io.IOException; +import java.util.Date; +import java.util.logging.Level; + +import net.minecraft.server.management.UserListIPBans; +import net.minecraft.server.management.UserListIPBansEntry; +import org.bukkit.Bukkit; + +public final class CraftIpBanEntry implements org.bukkit.BanEntry { + private final UserListIPBans list; + private final String target; + private Date created; + private String source; + private Date expiration; + private String reason; + + public CraftIpBanEntry(String target, UserListIPBansEntry entry, UserListIPBans list) { + this.list = list; + this.target = target; + this.created = entry.getCreated() != null ? new Date(entry.getCreated().getTime()) : null; + this.source = entry.getSource(); + this.expiration = entry.getBanEndDate() != null ? new Date(entry.getBanEndDate().getTime()) : null; + this.reason = entry.getBanReason(); + } + + @Override + public String getTarget() { + return this.target; + } + + @Override + public Date getCreated() { + return this.created == null ? null : (Date) this.created.clone(); + } + + @Override + public void setCreated(Date created) { + this.created = created; + } + + @Override + public String getSource() { + return this.source; + } + + @Override + public void setSource(String source) { + this.source = source; + } + + @Override + public Date getExpiration() { + return this.expiration == null ? null : (Date) this.expiration.clone(); + } + + @Override + public void setExpiration(Date expiration) { + if (expiration != null && expiration.getTime() == new Date(0, 0, 0, 0, 0, 0).getTime()) { + expiration = null; // Forces "forever" + } + + this.expiration = expiration; + } + + @Override + public String getReason() { + return this.reason; + } + + @Override + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public void save() { + UserListIPBansEntry entry = new UserListIPBansEntry(target, this.created, this.source, this.expiration, this.reason); + this.list.addEntry(entry); + try { + this.list.writeChanges(); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-ips.json, {0}", ex.getMessage()); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftIpBanList.java b/src/main/java/org/bukkit/craftbukkit/CraftIpBanList.java new file mode 100644 index 00000000..4b6adc24 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftIpBanList.java @@ -0,0 +1,78 @@ +package org.bukkit.craftbukkit; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Date; +import java.util.Set; + +import net.minecraft.server.management.UserListIPBans; +import net.minecraft.server.management.UserListIPBansEntry; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; + +import com.google.common.collect.ImmutableSet; +import java.util.logging.Level; +import org.bukkit.Bukkit; + +public class CraftIpBanList implements org.bukkit.BanList { + private final UserListIPBans list; + + public CraftIpBanList(UserListIPBans list) { + this.list = list; + } + + @Override + public org.bukkit.BanEntry getBanEntry(String target) { + Validate.notNull(target, "Target cannot be null"); + + UserListIPBansEntry entry = (UserListIPBansEntry) list.getEntry(target); + if (entry == null) { + return null; + } + + return new CraftIpBanEntry(target, entry, list); + } + + @Override + public org.bukkit.BanEntry addBan(String target, String reason, Date expires, String source) { + Validate.notNull(target, "Ban target cannot be null"); + + UserListIPBansEntry entry = new UserListIPBansEntry(target, new Date(), + StringUtils.isBlank(source) ? null : source, expires, + StringUtils.isBlank(reason) ? null : reason); + + list.addEntry(entry); + + try { + list.writeChanges(); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-ips.json, {0}", ex.getMessage()); + } + + return new CraftIpBanEntry(target, entry, list); + } + + @Override + public Set getBanEntries() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (String target : list.getKeys()) { + builder.add(new CraftIpBanEntry(target, (UserListIPBansEntry) list.getEntry(target), list)); + } + + return builder.build(); + } + + @Override + public boolean isBanned(String target) { + Validate.notNull(target, "Target cannot be null"); + + return list.isBanned(InetSocketAddress.createUnresolved(target, 0)); + } + + @Override + public void pardon(String target) { + Validate.notNull(target, "Target cannot be null"); + + list.removeEntry(target); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java new file mode 100644 index 00000000..e6601f61 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java @@ -0,0 +1,259 @@ +package org.bukkit.craftbukkit; + +import com.mojang.authlib.GameProfile; +import java.io.File; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.storage.SaveHandler; +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.Server; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.entity.Player; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +@SerializableAs("Player") +public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable { + private final GameProfile profile; + private final CraftServer server; + private final SaveHandler storage; + + protected CraftOfflinePlayer(CraftServer server, GameProfile profile) { + this.server = server; + this.profile = profile; + this.storage = (SaveHandler) (server.console.worldServerList.get(0).getSaveHandler()); + + } + + public GameProfile getProfile() { + return profile; + } + + public boolean isOnline() { + return getPlayer() != null; + } + + public String getName() { + Player player = getPlayer(); + if (player != null) { + return player.getName(); + } + + // This might not match lastKnownName but if not it should be more correct + if (profile.getName() != null) { + return profile.getName(); + } + + NBTTagCompound data = getBukkitData(); + + if (data != null) { + if (data.hasKey("lastKnownName")) { + return data.getString("lastKnownName"); + } + } + + return null; + } + + public UUID getUniqueId() { + return profile.getId(); + } + + public Server getServer() { + return server; + } + + public boolean isOp() { + return server.getHandle().canSendCommands(profile); + } + + public void setOp(boolean value) { + if (value == isOp()) { + return; + } + + if (value) { + server.getHandle().addOp(profile); + } else { + server.getHandle().removeOp(profile); + } + } + + public boolean isBanned() { + if (getName() == null) { + return false; + } + + return server.getBanList(BanList.Type.NAME).isBanned(getName()); + } + + public void setBanned(boolean value) { + if (getName() == null) { + return; + } + + if (value) { + server.getBanList(BanList.Type.NAME).addBan(getName(), null, null, null); + } else { + server.getBanList(BanList.Type.NAME).pardon(getName()); + } + } + + public boolean isWhitelisted() { + return server.getHandle().getWhitelistedPlayers().isWhitelisted(profile); + } + + public void setWhitelisted(boolean value) { + if (value) { + server.getHandle().addWhitelistedPlayer(profile); + } else { + server.getHandle().removePlayerFromWhitelist(profile); + } + } + + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("UUID", profile.getId().toString()); + + return result; + } + + public static OfflinePlayer deserialize(Map args) { + // Backwards comparability + if (args.get("name") != null) { + return Bukkit.getServer().getOfflinePlayer((String) args.get("name")); + } + + return Bukkit.getServer().getOfflinePlayer(UUID.fromString((String) args.get("UUID"))); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[UUID=" + profile.getId() + "]"; + } + + public Player getPlayer() { + return server.getPlayer(getUniqueId()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof OfflinePlayer)) { + return false; + } + + OfflinePlayer other = (OfflinePlayer) obj; + if ((this.getUniqueId() == null) || (other.getUniqueId() == null)) { + return false; + } + + return this.getUniqueId().equals(other.getUniqueId()); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + (this.getUniqueId() != null ? this.getUniqueId().hashCode() : 0); + return hash; + } + + private NBTTagCompound getData() { + return storage.getPlayerData(getUniqueId().toString()); + } + + private NBTTagCompound getBukkitData() { + NBTTagCompound result = getData(); + + if (result != null) { + if (!result.hasKey("bukkit")) { + result.setTag("bukkit", new NBTTagCompound()); + } + result = result.getCompoundTag("bukkit"); + } + + return result; + } + + private File getDataFile() { + return new File(storage.getPlayerDir(), getUniqueId() + ".dat"); + } + + public long getFirstPlayed() { + Player player = getPlayer(); + if (player != null) return player.getFirstPlayed(); + + NBTTagCompound data = getBukkitData(); + + if (data != null) { + if (data.hasKey("firstPlayed")) { + return data.getLong("firstPlayed"); + } else { + File file = getDataFile(); + return file.lastModified(); + } + } else { + return 0; + } + } + + public long getLastPlayed() { + Player player = getPlayer(); + if (player != null) return player.getLastPlayed(); + + NBTTagCompound data = getBukkitData(); + + if (data != null) { + if (data.hasKey("lastPlayed")) { + return data.getLong("lastPlayed"); + } else { + File file = getDataFile(); + return file.lastModified(); + } + } else { + return 0; + } + } + + public boolean hasPlayedBefore() { + return getData() != null; + } + + public Location getBedSpawnLocation() { + NBTTagCompound data = getData(); + if (data == null) return null; + + if (data.hasKey("SpawnX") && data.hasKey("SpawnY") && data.hasKey("SpawnZ")) { + String spawnWorld = data.getString("SpawnWorld"); + if (spawnWorld.equals("")) { + spawnWorld = server.getWorlds().get(0).getName(); + } + return new Location(server.getWorld(spawnWorld), data.getInteger("SpawnX"), data.getInteger("SpawnY"), data.getInteger("SpawnZ")); + } + return null; + } + + public void setMetadata(String metadataKey, MetadataValue metadataValue) { + server.getPlayerMetadata().setMetadata(this, metadataKey, metadataValue); + } + + public List getMetadata(String metadataKey) { + return server.getPlayerMetadata().getMetadata(this, metadataKey); + } + + public boolean hasMetadata(String metadataKey) { + return server.getPlayerMetadata().hasMetadata(this, metadataKey); + } + + public void removeMetadata(String metadataKey, Plugin plugin) { + server.getPlayerMetadata().removeMetadata(this, metadataKey, plugin); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftParticle.java b/src/main/java/org/bukkit/craftbukkit/CraftParticle.java new file mode 100644 index 00000000..79dd7ff4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftParticle.java @@ -0,0 +1,38 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.util.EnumParticleTypes; +import org.bukkit.Particle; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +public class CraftParticle { + + public static EnumParticleTypes toNMS(Particle bukkit) { + return EnumParticleTypes.valueOf(bukkit.name()); + } + + public static Particle toBukkit(EnumParticleTypes nms) { + return Particle.valueOf(nms.name()); + } + + public static int[] toData(Particle particle, Object obj) { + if (particle.getDataType().equals(Void.class)) { + return new int[0]; + } + if (particle.getDataType().equals(ItemStack.class)) { + if (obj == null) { + return new int[]{0, 0}; + } + ItemStack itemStack = (ItemStack) obj; + return new int[]{itemStack.getType().getId(), itemStack.getDurability()}; + } + if (particle.getDataType().equals(MaterialData.class)) { + if (obj == null) { + return new int[]{0}; + } + MaterialData data = (MaterialData) obj; + return new int[]{data.getItemTypeId() + ((int)(data.getData()) << 12)}; + } + throw new IllegalArgumentException(particle.getDataType().toString()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftProfileBanEntry.java b/src/main/java/org/bukkit/craftbukkit/CraftProfileBanEntry.java new file mode 100644 index 00000000..e74babbf --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftProfileBanEntry.java @@ -0,0 +1,89 @@ +package org.bukkit.craftbukkit; + +import com.mojang.authlib.GameProfile; + +import java.io.IOException; +import java.util.Date; +import java.util.logging.Level; + +import net.minecraft.server.management.UserListBans; +import net.minecraft.server.management.UserListBansEntry; +import org.bukkit.Bukkit; + +public final class CraftProfileBanEntry implements org.bukkit.BanEntry { + private final UserListBans list; + private final GameProfile profile; + private Date created; + private String source; + private Date expiration; + private String reason; + + public CraftProfileBanEntry(GameProfile profile, UserListBansEntry entry, UserListBans list) { + this.list = list; + this.profile = profile; + this.created = entry.getCreated() != null ? new Date(entry.getCreated().getTime()) : null; + this.source = entry.getSource(); + this.expiration = entry.getBanEndDate() != null ? new Date(entry.getBanEndDate().getTime()) : null; + this.reason = entry.getBanReason(); + } + + @Override + public String getTarget() { + return this.profile.getName(); + } + + @Override + public Date getCreated() { + return this.created == null ? null : (Date) this.created.clone(); + } + + @Override + public void setCreated(Date created) { + this.created = created; + } + + @Override + public String getSource() { + return this.source; + } + + @Override + public void setSource(String source) { + this.source = source; + } + + @Override + public Date getExpiration() { + return this.expiration == null ? null : (Date) this.expiration.clone(); + } + + @Override + public void setExpiration(Date expiration) { + if (expiration != null && expiration.getTime() == new Date(0, 0, 0, 0, 0, 0).getTime()) { + expiration = null; // Forces "forever" + } + + this.expiration = expiration; + } + + @Override + public String getReason() { + return this.reason; + } + + @Override + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public void save() { + UserListBansEntry entry = new UserListBansEntry(profile, this.created, this.source, this.expiration, this.reason); + this.list.addEntry(entry); + try { + this.list.writeChanges(); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-players.json, {0}", ex.getMessage()); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftProfileBanList.java b/src/main/java/org/bukkit/craftbukkit/CraftProfileBanList.java new file mode 100644 index 00000000..901add7d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftProfileBanList.java @@ -0,0 +1,99 @@ +package org.bukkit.craftbukkit; + +import java.io.IOException; +import java.util.Date; +import java.util.Set; + +import net.minecraft.server.MinecraftServer; + +import net.minecraft.server.management.UserListBans; +import net.minecraft.server.management.UserListBansEntry; +import net.minecraft.server.management.UserListEntry; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; + +import com.google.common.collect.ImmutableSet; +import com.mojang.authlib.GameProfile; +import java.util.logging.Level; +import org.bukkit.Bukkit; + +public class CraftProfileBanList implements org.bukkit.BanList { + private final UserListBans list; + + public CraftProfileBanList(UserListBans list){ + this.list = list; + } + + @Override + public org.bukkit.BanEntry getBanEntry(String target) { + Validate.notNull(target, "Target cannot be null"); + + GameProfile profile = MinecraftServer.getServerCB().getPlayerProfileCache().getGameProfileForUsername(target); + if (profile == null) { + return null; + } + + UserListBansEntry entry = list.getEntry(profile); + if (entry == null) { + return null; + } + + return new CraftProfileBanEntry(profile, entry, list); + } + + @Override + public org.bukkit.BanEntry addBan(String target, String reason, Date expires, String source) { + Validate.notNull(target, "Ban target cannot be null"); + + GameProfile profile = MinecraftServer.getServerCB().getPlayerProfileCache().getGameProfileForUsername(target); + if (profile == null) { + return null; + } + + UserListBansEntry entry = new UserListBansEntry(profile, new Date(), + StringUtils.isBlank(source) ? null : source, expires, + StringUtils.isBlank(reason) ? null : reason); + + list.addEntry(entry); + + try { + list.writeChanges(); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-players.json, {0}", ex.getMessage()); + } + + return new CraftProfileBanEntry(profile, entry, list); + } + + @Override + public Set getBanEntries() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + + for (UserListEntry entry : list.getValuesCB()) { + GameProfile profile = (GameProfile) entry.getValue(); + builder.add(new CraftProfileBanEntry(profile, (UserListBansEntry) entry, list)); + } + + return builder.build(); + } + + @Override + public boolean isBanned(String target) { + Validate.notNull(target, "Target cannot be null"); + + GameProfile profile = MinecraftServer.getServerCB().getPlayerProfileCache().getGameProfileForUsername(target); + if (profile == null) { + return false; + } + + return list.isBanned(profile); + } + + @Override + public void pardon(String target) { + Validate.notNull(target, "Target cannot be null"); + + GameProfile profile = MinecraftServer.getServerCB().getPlayerProfileCache().getGameProfileForUsername(target); + list.removeEntry(profile); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java new file mode 100644 index 00000000..67007b0d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -0,0 +1,1762 @@ +package org.bukkit.craftbukkit; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; + +import jline.console.ConsoleReader; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandSenderWrapper; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.EntityTracker; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Enchantments; +import net.minecraft.init.Items; +import net.minecraft.init.MobEffects; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.server.*; + +import net.minecraft.server.dedicated.DedicatedPlayerList; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.PendingCommand; +import net.minecraft.server.dedicated.PropertyManager; +import net.minecraft.server.management.PlayerList; +import net.minecraft.server.management.UserListEntry; +import net.minecraft.util.IProgressUpdate; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.RegistryNamespaced; +import net.minecraft.util.text.translation.I18n; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.GameType; +import net.minecraft.world.MinecraftException; +import net.minecraft.world.ServerWorldEventHandler; +import net.minecraft.world.WorldServer; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.storage.AnvilSaveConverter; +import net.minecraft.world.chunk.storage.AnvilSaveHandler; +import net.minecraft.world.storage.ISaveFormat; +import net.minecraft.world.storage.ISaveHandler; +import net.minecraft.world.storage.MapData; +import net.minecraft.world.storage.MapStorage; +import net.minecraft.world.storage.SaveHandler; +import net.minecraft.world.storage.WorldInfo; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.FMLCommonHandler; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.Server; +import org.bukkit.UnsafeValues; +import org.bukkit.Warning.WarningState; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.command.Command; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.conversations.Conversable; +import org.bukkit.craftbukkit.boss.CraftBossBar; +import org.bukkit.craftbukkit.command.CraftSimpleCommandMap; +import org.bukkit.craftbukkit.command.UnknownCommandEvent; +import org.bukkit.craftbukkit.command.VanillaCommandWrapper; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.generator.CraftChunkData; +import org.bukkit.craftbukkit.help.SimpleHelpMap; +import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; +import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; +import org.bukkit.craftbukkit.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.inventory.CraftMerchantCustom; +import org.bukkit.craftbukkit.inventory.CraftRecipe; +import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; +import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; +import org.bukkit.craftbukkit.inventory.RecipeIterator; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.craftbukkit.metadata.EntityMetadataStore; +import org.bukkit.craftbukkit.metadata.PlayerMetadataStore; +import org.bukkit.craftbukkit.metadata.WorldMetadataStore; +import org.bukkit.craftbukkit.potion.CraftPotionBrewer; +import org.bukkit.craftbukkit.scheduler.CraftScheduler; +import org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager; +import org.bukkit.craftbukkit.util.CraftIconCache; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.DatFileFilter; +import org.bukkit.craftbukkit.util.Versioning; +import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerChatTabCompleteEvent; +import org.bukkit.event.server.BroadcastMessageEvent; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginLoadOrder; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.SimplePluginManager; +import org.bukkit.plugin.SimpleServicesManager; +import org.bukkit.plugin.java.JavaPluginLoader; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.plugin.messaging.StandardMessenger; +import org.bukkit.scheduler.BukkitWorker; +import org.bukkit.util.StringUtil; +import org.bukkit.util.permissions.DefaultPermissions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.MarkedYAMLException; + +import org.apache.commons.lang3.Validate; + +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; +import com.mojang.authlib.GameProfile; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +//import jline.console.ConsoleReader; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.event.server.TabCompleteEvent; +import net.md_5.bungee.api.chat.BaseComponent; + +public final class CraftServer implements Server { + private final String serverName = "Kettle"; + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); + private final ServicesManager servicesManager = new SimpleServicesManager(); + private final CraftScheduler scheduler = new CraftScheduler(); + private final CraftSimpleCommandMap craftCommandMap = new CraftSimpleCommandMap(this); + private final SimpleCommandMap commandMap = new SimpleCommandMap(this); + private final SimpleHelpMap helpMap = new SimpleHelpMap(this); + private final StandardMessenger messenger = new StandardMessenger(); + private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap); + protected final MinecraftServer console; + protected final DedicatedPlayerList playerList; + private final Map worlds = new LinkedHashMap(); + private YamlConfiguration configuration; + private YamlConfiguration commandsConfiguration; + private final Yaml yaml = new Yaml(new SafeConstructor()); + private final Map offlinePlayers = new MapMaker().weakValues().makeMap(); + private final EntityMetadataStore entityMetadata = new EntityMetadataStore(); + private final PlayerMetadataStore playerMetadata = new PlayerMetadataStore(); + private final WorldMetadataStore worldMetadata = new WorldMetadataStore(); + private int monsterSpawn = -1; + private int animalSpawn = -1; + private int waterAnimalSpawn = -1; + private int ambientSpawn = -1; + public int chunkGCPeriod = -1; + public int chunkGCLoadThresh = 0; + private File container; + private WarningState warningState = WarningState.DEFAULT; + private final BooleanWrapper online = new BooleanWrapper(); + public CraftScoreboardManager scoreboardManager; + public boolean playerCommandState; + private boolean printSaveWarning; + private CraftIconCache icon; + private boolean overrideAllCommandBlockCommands = false; + private boolean unrestrictedAdvancements; + private final List playerView; + public int reloadCount; + + private final class BooleanWrapper { + private boolean value = true; + } + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + CraftItemFactory.instance(); + } + + public CraftServer(MinecraftServer console, PlayerList playerList) { + this.console = console; + this.playerList = (DedicatedPlayerList) playerList; + this.playerView = Collections + .unmodifiableList(Lists.transform(playerList.getPlayers(), new Function() { + @Override + public CraftPlayer apply(EntityPlayerMP player) { + return player.getBukkitEntity(); + } + })); + this.serverVersion = CraftServer.class.getPackage().getImplementationVersion(); + online.value = console.getPropertyManager().getBooleanProperty("online-mode", true); + + Bukkit.setServer(this); + + // Register all the Enchantments and PotionTypes now so we can stop new registration immediately after + Enchantments.SHARPNESS.getClass(); + + Potion.setPotionBrewer(new CraftPotionBrewer()); + MobEffects.BLINDNESS.getClass(); + // Ugly hack :( + + if (!Main.useConsole) { + getLogger().info("Console input is disabled due to --noconsole command argument"); + } + + configuration = YamlConfiguration.loadConfiguration(getConfigFile()); + configuration.options().copyDefaults(true); + configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader( + getClass().getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8))); + ConfigurationSection legacyAlias = null; + if (!configuration.isString("aliases")) { + legacyAlias = configuration.getConfigurationSection("aliases"); + configuration.set("aliases", "now-in-commands.yml"); + } + saveConfig(); + if (getCommandsConfigFile().isFile()) { + legacyAlias = null; + } + commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile()); + commandsConfiguration.options().copyDefaults(true); + commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader( + getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); + saveCommandsConfig(); + + // Migrate aliases from old file and add previously implicit $1- to pass all arguments + if (legacyAlias != null) { + ConfigurationSection aliases = commandsConfiguration.createSection("aliases"); + for (String key : legacyAlias.getKeys(false)) { + ArrayList commands = new ArrayList(); + + if (legacyAlias.isList(key)) { + for (String command : legacyAlias.getStringList(key)) { + commands.add(command + " $1-"); + } + } else { + commands.add(legacyAlias.getString(key) + " $1-"); + } + + aliases.set(key, commands); + } + } + + saveCommandsConfig(); + overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*"); + unrestrictedAdvancements = commandsConfiguration.getBoolean("unrestricted-advancements"); + pluginManager.useTimings(configuration.getBoolean("settings.plugin-profiling")); + monsterSpawn = configuration.getInt("spawn-limits.monsters"); + animalSpawn = configuration.getInt("spawn-limits.animals"); + waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals"); + ambientSpawn = configuration.getInt("spawn-limits.ambient"); + console.autosavePeriod = configuration.getInt("ticks-per.autosave"); + warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); + loadIcon(); // Fixed server ping stalling. + chunkGCPeriod = configuration.getInt("chunk-gc.period-in-ticks"); + chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold"); + loadIcon(); + } + + public boolean getPermissionOverride(ICommandSender listener) { + while (listener instanceof CommandSenderWrapper) { + listener = ((CommandSenderWrapper) listener).delegate; + } + return unrestrictedAdvancements && listener instanceof AdvancementRewards.AdvancementCommandListener; + } + + public boolean getCommandBlockOverride(String command) { + return overrideAllCommandBlockCommands + || commandsConfiguration.getStringList("command-block-overrides").contains(command); + } + + private File getConfigFile() { + return (File) console.options.valueOf("bukkit-settings"); + } + + private File getCommandsConfigFile() { + return (File) console.options.valueOf("commands-settings"); + } + + private void saveConfig() { + try { + configuration.save(getConfigFile()); + } catch (IOException ex) { + Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getConfigFile(), ex); + } + } + + private void saveCommandsConfig() { + try { + commandsConfiguration.save(getCommandsConfigFile()); + } catch (IOException ex) { + Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getCommandsConfigFile(), + ex); + } + } + + public void loadPlugins() { + pluginManager.registerInterface(JavaPluginLoader.class); + + File pluginFolder = (File) console.options.valueOf("plugins"); + + if (pluginFolder.exists()) { + Plugin[] plugins = pluginManager.loadPlugins(pluginFolder); + for (Plugin plugin : plugins) { + try { + String message = String.format("Loading %s", plugin.getDescription().getFullName()); + plugin.getLogger().info(message); + plugin.onLoad(); + } catch (Throwable ex) { + Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " initializing " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + } + } else { + pluginFolder.mkdir(); + } + } + + public void enablePlugins(PluginLoadOrder type) { + if (type == PluginLoadOrder.STARTUP) { + helpMap.clear(); + helpMap.initializeGeneralTopics(); + loadCustomPermissions(); // Paper + } + + Plugin[] plugins = pluginManager.getPlugins(); + + for (Plugin plugin : plugins) { + if ((!plugin.isEnabled()) && (plugin.getDescription().getLoad() == type)) { + enablePlugin(plugin); + } + } + + if (type == PluginLoadOrder.POSTWORLD) { + // Spigot start - Allow vanilla commands to be forced to be the main command + setVanillaCommands(true); + commandMap.setFallbackCommands(); + this.setVanillaCommands(false); + // Spigot end + commandMap.registerServerAliases(); + loadCustomPermissions(); + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); + helpMap.initializeCommands(); + } + + } + + @Override + public boolean suggestPlayerNamesWhenNullTabCompletions() { + return true; + } + + public void disablePlugins() { + pluginManager.disablePlugins(); + } + + private void setVanillaCommands(boolean first) { + Map commands = console.getCommandManager().getCommands(); + for (ICommand cmd : commands.values()) { + commandMap.register("minecraft", + new VanillaCommandWrapper((CommandBase) cmd, I18n.translateToLocal(cmd.getUsage(null)))); + } + } + + private void enablePlugin(Plugin plugin) { + try { + List perms = plugin.getDescription().getPermissions(); + + for (Permission perm : perms) { + try { + pluginManager.addPermission(perm, false); + } catch (IllegalArgumentException ex) { + getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); + } + } + pluginManager.dirtyPermissibles(); + + pluginManager.enablePlugin(plugin); + } catch (Throwable ex) { + Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, + ex.getMessage() + " loading " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + } + + @Override + public String getName() { + return serverName; + } + + @Override + public String getVersion() { + return serverVersion + " (MC: " + console.getMinecraftVersion() + ")"; + } + + @Override + public String getBukkitVersion() { + return bukkitVersion; + } + + @Override + public Player[] _INVALID_getOnlinePlayers() { + return getOnlinePlayers().toArray(new Player[0]); + } + + @Override + public List getOnlinePlayers() { + return this.playerView; + } + + @Override + @Deprecated + public Player getPlayer(final String name) { + Validate.notNull(name, "Name cannot be null"); + + Player found = getPlayerExact(name); + // Try for an exact match first. + if (found != null) { + return found; + } + + String lowerName = name.toLowerCase(java.util.Locale.ENGLISH); + int delta = Integer.MAX_VALUE; + for (Player player : getOnlinePlayers()) { + if (player.getName().toLowerCase(java.util.Locale.ENGLISH).startsWith(lowerName)) { + int curDelta = Math.abs(player.getName().length() - lowerName.length()); + if (curDelta < delta) { + found = player; + delta = curDelta; + } + if (curDelta == 0) { + break; + } + } + } + return found; + } + + @Override + @Deprecated + public Player getPlayerExact(String name) { + Validate.notNull(name, "Name cannot be null"); + + EntityPlayerMP player = playerList.getPlayerByUsername(name); + return (player != null) ? player.getBukkitEntity() : null; + } + + @Override + public Player getPlayer(UUID id) { + EntityPlayerMP player = playerList.getPlayerByUUID(id); + + if (player != null) { + return player.getBukkitEntity(); + } + + return null; + } + + @Override + public int broadcastMessage(String message) { + return broadcast(message, BROADCAST_CHANNEL_USERS); + } + + public Player getPlayer(final EntityPlayerMP entity) { + return entity.getBukkitEntity(); + } + + @Override + @Deprecated + public List matchPlayer(String partialName) { + Validate.notNull(partialName, "PartialName cannot be null"); + + List matchedPlayers = new ArrayList(); + + for (Player iterPlayer : this.getOnlinePlayers()) { + String iterPlayerName = iterPlayer.getName(); + + if (partialName.equalsIgnoreCase(iterPlayerName)) { + // Exact match + matchedPlayers.clear(); + matchedPlayers.add(iterPlayer); + break; + } + if (iterPlayerName.toLowerCase(java.util.Locale.ENGLISH) + .contains(partialName.toLowerCase(java.util.Locale.ENGLISH))) { + // Partial match + matchedPlayers.add(iterPlayer); + } + } + + return matchedPlayers; + } + + @Override + public int getMaxPlayers() { + return playerList.getMaxPlayers(); + } + + // NOTE: These are dependent on the corresponding call in MinecraftServer + // so if that changes this will need to as well + @Override + public int getPort() { + return this.getConfigInt("server-port", 25565); + } + + @Override + public int getViewDistance() { + return this.getConfigInt("view-distance", 10); + } + + @Override + public String getIp() { + return this.getConfigString("server-ip", ""); + } + + @Override + public String getServerName() { + return this.getConfigString("server-name", "Unknown Server"); + } + + @Override + public String getServerId() { + return this.getConfigString("server-id", "unnamed"); + } + + @Override + public String getWorldType() { + return this.getConfigString("level-type", "DEFAULT"); + } + + @Override + public boolean getGenerateStructures() { + return this.getConfigBoolean("generate-structures", true); + } + + @Override + public boolean getAllowEnd() { + return this.configuration.getBoolean("settings.allow-end"); + } + + @Override + public boolean getAllowNether() { + return this.getConfigBoolean("allow-nether", true); + } + + public boolean getWarnOnOverload() { + return this.configuration.getBoolean("settings.warn-on-overload"); + } + + public boolean getQueryPlugins() { + return this.configuration.getBoolean("settings.query-plugins"); + } + + @Override + public boolean hasWhitelist() { + return this.getConfigBoolean("white-list", false); + } + + // NOTE: Temporary calls through to server.properies until its replaced + private String getConfigString(String variable, String defaultValue) { + return this.console.getPropertyManager().getStringProperty(variable, defaultValue); + } + + private int getConfigInt(String variable, int defaultValue) { + return this.console.getPropertyManager().getIntProperty(variable, defaultValue); + } + + private boolean getConfigBoolean(String variable, boolean defaultValue) { + return this.console.getPropertyManager().getBooleanProperty(variable, defaultValue); + } + + // End Temporary calls + + @Override + public String getUpdateFolder() { + return this.configuration.getString("settings.update-folder", "update"); + } + + @Override + public File getUpdateFolderFile() { + return new File((File) console.options.valueOf("plugins"), + this.configuration.getString("settings.update-folder", "update")); + } + + @Override + public long getConnectionThrottle() { + // Spigot Start - Automatically set connection throttle for bungee configurations + if (org.spigotmc.SpigotConfig.bungee) { + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); + } + // Spigot End + + } + + @Override + public int getTicksPerAnimalSpawns() { + return this.configuration.getInt("ticks-per.animal-spawns"); + } + + @Override + public int getTicksPerMonsterSpawns() { + return this.configuration.getInt("ticks-per.monster-spawns"); + } + + @Override + public PluginManager getPluginManager() { + return pluginManager; + } + + @Override + public CraftScheduler getScheduler() { + return scheduler; + } + + @Override + public ServicesManager getServicesManager() { + return servicesManager; + } + + @Override + public List getWorlds() { + return new ArrayList(worlds.values()); + } + + public DedicatedPlayerList getHandle() { + return playerList; + } + + // NOTE: Should only be called from DedicatedServer.ah() + public boolean dispatchServerCommand(CommandSender sender, PendingCommand serverCommand) { + if (sender instanceof Conversable) { + Conversable conversable = (Conversable) sender; + + if (conversable.isConversing()) { + conversable.acceptConversationInput(serverCommand.command); + return true; + } + } + try { + this.playerCommandState = true; + return this.dispatchCommand(sender, serverCommand.command); + } catch (Exception ex) { + getLogger().log(Level.WARNING, + "Unexpected exception while parsing console command \"" + serverCommand.command + '"', ex); + return false; + } finally { + this.playerCommandState = false; + } + } + + @Override + public boolean dispatchCommand(CommandSender sender, String commandLine) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(commandLine, "CommandLine cannot be null"); + + if (commandMap.dispatch(sender, commandLine)) { + return true; + } + + if (sender instanceof ConsoleCommandSender) { + craftCommandMap.setVanillaConsoleSender(this.console); + } + + return this.dispatchVanillaCommand(sender, commandLine); + } + + // used to process vanilla commands + public boolean dispatchVanillaCommand(CommandSender sender, String commandLine) { + if (craftCommandMap.dispatch(sender, commandLine)) { + return true; + } + if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { + // Paper start + UnknownCommandEvent event = new UnknownCommandEvent(sender, commandLine, org.spigotmc.SpigotConfig.unknownCommandMessage); + Bukkit.getServer().getPluginManager().callEvent(event); + if (StringUtils.isNotEmpty(event.getMessage())) { + sender.sendMessage(event.getMessage()); + } + // Paper end + } + + return false; + } + + @Override + public void reload() { + } + + @Override + public void reloadData() { + console.reload(); + } + + private void loadIcon() { + icon = new CraftIconCache(null); + try { + final File file = new File(new File("."), "server-icon.png"); + if (file.isFile()) { + icon = loadServerIcon0(file); + } + } catch (Exception ex) { + getLogger().log(Level.WARNING, "Couldn't load server icon", ex); + } + } + + @SuppressWarnings({ "unchecked", "finally" }) + private void loadCustomPermissions() { + File file = new File(configuration.getString("settings.permissions-file")); + FileInputStream stream; + + try { + stream = new FileInputStream(file); + } catch (FileNotFoundException ex) { + try { + file.createNewFile(); + } finally { + return; + } + } + + Map> perms; + + try { + perms = (Map>) yaml.load(stream); + } catch (MarkedYAMLException ex) { + getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML: " + ex.toString()); + return; + } catch (Throwable ex) { + getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML.", ex); + return; + } finally { + try { + stream.close(); + } catch (IOException ex) { + } + } + + if (perms == null) { + getLogger().log(Level.INFO, "Server permissions file " + file + " is empty, ignoring it"); + return; + } + + List permsList = Permission.loadPermissions(perms, + "Permission node '%s' in " + file + " is invalid", Permission.DEFAULT_PERMISSION); + + for (Permission perm : permsList) { + try { + pluginManager.addPermission(perm); + } catch (IllegalArgumentException ex) { + getLogger().log(Level.SEVERE, "Permission in " + file + " was already defined", ex); + } + } + } + + @Override + public String toString() { + return "CraftServer{" + "serverName=" + serverName + ",serverVersion=" + serverVersion + ",minecraftVersion=" + + console.getMinecraftVersion() + '}'; + } + + public World createWorld(String name, Environment environment) { + return WorldCreator.name(name).environment(environment).createWorld(); + } + + public World createWorld(String name, Environment environment, long seed) { + return WorldCreator.name(name).environment(environment).seed(seed).createWorld(); + } + + public World createWorld(String name, Environment environment, ChunkGenerator generator) { + return WorldCreator.name(name).environment(environment).generator(generator).createWorld(); + } + + public World createWorld(String name, Environment environment, long seed, ChunkGenerator generator) { + return WorldCreator.name(name).environment(environment).seed(seed).generator(generator).createWorld(); + } + + @Override + public World createWorld(WorldCreator creator) { + Validate.notNull(creator, "Creator may not be null"); + + String name = creator.name(); + ChunkGenerator generator = creator.generator(); + File folder = new File(getWorldContainer(), name); + World world = getWorld(name); + WorldType type = WorldType.parseWorldType(creator.type().getName()); + boolean generateStructures = creator.generateStructures(); + + if ((folder.exists()) && (!folder.isDirectory())) { + throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder"); + } + + if (world != null) { + return world; + } + + boolean hardcore = false; + + WorldSettings worldSettings = new WorldSettings(creator.seed(), WorldSettings.getGameTypeById(getDefaultGameMode().getValue()), generateStructures, hardcore, type); + WorldServer internal = DimensionManager.initDimension(creator, worldSettings); + + pluginManager.callEvent(new WorldInitEvent(internal.getWorld())); + System.out.println("Preparing start region for level " + (console.worldServerList.size() - 1) + " (Seed: " + internal.getSeed() + ")"); + + if (internal.getWorld().getKeepSpawnInMemory()) { + short short1 = 196; + long i = System.currentTimeMillis(); + for (int j = -short1; j <= short1; j += 16) { + for (int k = -short1; k <= short1; k += 16) { + long l = System.currentTimeMillis(); + + if (l < i) { + i = l; + } + + if (l > i + 1000L) { + int i1 = (short1 * 2 + 1) * (short1 * 2 + 1); + int j1 = (j + short1) * (short1 * 2 + 1) + k + 1; + + System.out.println("Preparing spawn area for " + name + ", " + (j1 * 100 / i1) + "%"); + i = l; + } + + BlockPos chunkcoordinates = internal.getSpawnPoint(); + internal.getChunkProvider().loadChunk(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4); + } + } + } + pluginManager.callEvent(new WorldLoadEvent(internal.getWorld())); + return internal.getWorld(); + } + + @Override + public boolean unloadWorld(String name, boolean save) { + return unloadWorld(getWorld(name), save); + } + + @Override + public boolean unloadWorld(World world, boolean save) { + if (world == null) { + return false; + } + + WorldServer handle = ((CraftWorld) world).getHandle(); + + if (!(console.worldServerList.contains(handle))) { + return false; + } + + if (handle.dimension == 0) { + return false; + } + + if (handle.playerEntities.size() > 0) { + return false; + } + + WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld()); + pluginManager.callEvent(e); + + if (e.isCancelled()) { + return false; + } + + if (save) { + try { + handle.saveAllChunks(true, null); + handle.flush(); + } catch (MinecraftException ex) { + getLogger().log(Level.SEVERE, null, ex); + } + } + MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.WorldEvent.Unload(handle)); // fire unload event before removing world + worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH)); + DimensionManager.setWorld(handle.provider.getDimension(), null, FMLCommonHandler.instance().getMinecraftServerInstance()); // remove world from DimensionManager + + return true; + } + + public MinecraftServer getServer() { + return console; + } + + @Override + public World getWorld(String name) { + Validate.notNull(name, "Name cannot be null"); + World world = worlds.get(name.toLowerCase(java.util.Locale.ENGLISH)); + if (world == null && name.toUpperCase().startsWith("DIM")) { + int dimension; + try { + dimension = Integer.valueOf(name.substring(3)); + WorldServer worldserver = console.getWorld(dimension); + if (worldserver != null) world = worldserver.getWorld(); + } catch (NumberFormatException e) {} + } + return world; + } + + @Override + public World getWorld(UUID uid) { + for (World world : worlds.values()) { + if (world.getUID().equals(uid)) { + return world; + } + } + return null; + } + + public void addWorld(World world) { + // Check if a World already exists with the UID. + if (getWorld(world.getUID()) != null) { + System.out.println("World " + world.getName() + " is a duplicate of another world and has been prevented from loading. Please delete the uid.dat file from " + world.getName() + "'s world directory if you want to be able to load the duplicate world."); + return; + } + worlds.put(world.getName().toLowerCase(java.util.Locale.ENGLISH), world); + } + + @Override + public Logger getLogger() { + return logger; + } + + public ConsoleReader getReader() { + return this.console.reader; + } + + @Override + public PluginCommand getPluginCommand(String name) { + Command command = commandMap.getCommand(name); + + if (command instanceof PluginCommand) { + return (PluginCommand) command; + } else { + return null; + } + } + + @Override + public void savePlayers() { + checkSaveState(); + playerList.saveAllPlayerData(); + } + + @Override + public boolean addRecipe(Recipe recipe) { + CraftRecipe toAdd; + if (recipe instanceof CraftRecipe) { + toAdd = (CraftRecipe) recipe; + } else { + if (recipe instanceof ShapedRecipe) { + toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe) recipe); + } else if (recipe instanceof ShapelessRecipe) { + toAdd = CraftShapelessRecipe.fromBukkitRecipe((ShapelessRecipe) recipe); + } else if (recipe instanceof FurnaceRecipe) { + toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe) recipe); + } else { + return false; + } + } + toAdd.addToCraftingManager(); + return true; + } + + @Override + public List getRecipesFor(ItemStack result) { + Validate.notNull(result, "Result cannot be null"); + + List results = new ArrayList(); + Iterator iter = recipeIterator(); + while (iter.hasNext()) { + Recipe recipe = iter.next(); + ItemStack stack = recipe.getResult(); + if (stack.getType() != result.getType()) { + continue; + } + if (result.getDurability() == -1 || result.getDurability() == stack.getDurability()) { + results.add(recipe); + } + } + return results; + } + + @Override + public Iterator recipeIterator() { + return new RecipeIterator(); + } + + @Override + public void clearRecipes() { + CraftingManager.REGISTRY = new RegistryNamespaced(); + FurnaceRecipes.instance().smeltingList.clear(); + FurnaceRecipes.instance().customRecipes.clear(); + FurnaceRecipes.instance().customExperience.clear(); + } + + @Override + public void resetRecipes() { + CraftingManager.REGISTRY = new RegistryNamespaced(); + CraftingManager.init(); + FurnaceRecipes.instance().smeltingList = new FurnaceRecipes().smeltingList; + FurnaceRecipes.instance().customRecipes.clear(); + FurnaceRecipes.instance().customExperience.clear(); + } + + @Override + public Map getCommandAliases() { + ConfigurationSection section = commandsConfiguration.getConfigurationSection("aliases"); + Map result = new LinkedHashMap(); + + if (section != null) { + for (String key : section.getKeys(false)) { + List commands; + + if (section.isList(key)) { + commands = section.getStringList(key); + } else { + commands = ImmutableList.of(section.getString(key)); + } + + result.put(key, commands.toArray(new String[commands.size()])); + } + } + + return result; + } + + public void removeBukkitSpawnRadius() { + configuration.set("settings.spawn-radius", null); + saveConfig(); + } + + public int getBukkitSpawnRadius() { + return configuration.getInt("settings.spawn-radius", -1); + } + + @Override + public String getShutdownMessage() { + return configuration.getString("settings.shutdown-message"); + } + + @Override + public int getSpawnRadius() { + return ((DedicatedServer) console).settings.getIntProperty("spawn-protection", 16); + } + + @Override + public void setSpawnRadius(int value) { + configuration.set("settings.spawn-radius", value); + saveConfig(); + } + + @Override + public boolean getOnlineMode() { + return online.value; + } + + @Override + public boolean getAllowFlight() { + return console.isFlightAllowed(); + } + + @Override + public boolean isHardcore() { + return console.isHardcore(); + } + + public ChunkGenerator getGenerator(String world) { + ConfigurationSection section = configuration.getConfigurationSection("worlds"); + ChunkGenerator result = null; + + if (section != null) { + section = section.getConfigurationSection(world); + + if (section != null) { + String name = section.getString("generator"); + + if ((name != null) && (!name.equals(""))) { + String[] split = name.split(":", 2); + String id = (split.length > 1) ? split[1] : null; + Plugin plugin = pluginManager.getPlugin(split[0]); + + if (plugin == null) { + getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + + split[0] + "' does not exist"); + } else if (!plugin.isEnabled()) { + getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + + plugin.getDescription().getFullName() + "' is not enabled yet (is it load:STARTUP?)"); + } else { + try { + result = plugin.getDefaultWorldGenerator(world, id); + if (result == null) { + getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + + plugin.getDescription().getFullName() + "' lacks a default world generator"); + } + } catch (Throwable t) { + plugin.getLogger().log(Level.SEVERE, "Could not set generator for default world '" + world + + "': Plugin '" + plugin.getDescription().getFullName(), t); + } + } + } + } + } + + return result; + } + + @Override + @Deprecated + public CraftMapView getMap(short id) { + MapStorage collection = console.worlds[0].mapStorage; + MapData worldmap = (MapData) collection.getOrLoadData(MapData.class, "map_" + id); + if (worldmap == null) { + return null; + } + return worldmap.mapView; + } + + @Override + public CraftMapView createMap(World world) { + Validate.notNull(world, "World cannot be null"); + + net.minecraft.item.ItemStack stack = new net.minecraft.item.ItemStack(Items.MAP, 1, -1); + MapData worldmap = Items.FILLED_MAP.getMapData(stack, ((CraftWorld) world).getHandle()); + return worldmap.mapView; + } + + @Override + public void shutdown() { + console.initiateShutdown(); + } + + @Override + public int broadcast(String message, String permission) { + Set recipients = new HashSet<>(); + for (Permissible permissible : getPluginManager().getPermissionSubscriptions(permission)) { + if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { + recipients.add((CommandSender) permissible); + } + } + + BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(message, recipients); + getPluginManager().callEvent(broadcastMessageEvent); + + if (broadcastMessageEvent.isCancelled()) { + return 0; + } + + message = broadcastMessageEvent.getMessage(); + + for (CommandSender recipient : recipients) { + recipient.sendMessage(message); + } + + return recipients.size(); + } + + // Paper start + @Override + @Nullable + public UUID getPlayerUniqueId(String name) { + Player player = Bukkit.getPlayerExact(name); + if (player != null) { + return player.getUniqueId(); + } + GameProfile profile; + // Only fetch an online UUID in online mode + if (MinecraftServer.getServerCB().isServerInOnlineMode() || (org.spigotmc.SpigotConfig.bungee)) { + profile = console.getPlayerProfileCache().getGameProfileForUsername( name ); + } else { + // Make an OfflinePlayer using an offline mode UUID since the name has no profile + profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); + } + return profile != null ? profile.getId() : null; + } + // Paper end + + @Override + @Deprecated + public OfflinePlayer getOfflinePlayer(String name) { + Validate.notNull(name, "Name cannot be null"); + + OfflinePlayer result = getPlayerExact(name); + if (result == null) { + // This is potentially blocking :( + GameProfile profile = console.getPlayerProfileCache().getGameProfileForUsername(name); + if (profile == null) { + // Make an OfflinePlayer using an offline mode UUID since the name has no profile + result = getOfflinePlayer(new GameProfile( + UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name)); + } else { + // Use the GameProfile even when we get a UUID so we ensure we still have a name + result = getOfflinePlayer(profile); + } + } else { + offlinePlayers.remove(result.getUniqueId()); + } + + return result; + } + + @Override + public OfflinePlayer getOfflinePlayer(UUID id) { + Validate.notNull(id, "UUID cannot be null"); + + OfflinePlayer result = getPlayer(id); + if (result == null) { + result = offlinePlayers.get(id); + if (result == null) { + result = new CraftOfflinePlayer(this, new GameProfile(id, null)); + offlinePlayers.put(id, result); + } + } else { + offlinePlayers.remove(id); + } + + return result; + } + + public OfflinePlayer getOfflinePlayer(GameProfile profile) { + OfflinePlayer player = new CraftOfflinePlayer(this, profile); + offlinePlayers.put(profile.getId(), player); + return player; + } + + @Override + @SuppressWarnings("unchecked") + public Set getIPBans() { + return new HashSet(Arrays.asList(playerList.getBannedIPs().getKeys())); + } + + @Override + public void banIP(String address) { + Validate.notNull(address, "Address cannot be null."); + + this.getBanList(BanList.Type.IP).addBan(address, null, null, null); + } + + @Override + public void unbanIP(String address) { + Validate.notNull(address, "Address cannot be null."); + + this.getBanList(BanList.Type.IP).pardon(address); + } + + @Override + public Set getBannedPlayers() { + Set result = new HashSet(); + + for (UserListEntry entry : playerList.getBannedPlayers().getValuesCB()) { + result.add(getOfflinePlayer((GameProfile) entry.getValue())); + } + + return result; + } + + @Override + public BanList getBanList(BanList.Type type) { + Validate.notNull(type, "Type cannot be null"); + + switch (type) { + case IP: + return new CraftIpBanList(playerList.getBannedIPs()); + case NAME: + default: + return new CraftProfileBanList(playerList.getBannedPlayers()); + } + } + + @Override + public void setWhitelist(boolean value) { + playerList.setWhiteListEnabled(value); + console.getPropertyManager().setProperty("white-list", value); + } + + @Override + public Set getWhitelistedPlayers() { + Set result = new LinkedHashSet(); + + for (UserListEntry entry : playerList.getWhitelistedPlayers().getValuesCB()) { + result.add(getOfflinePlayer((GameProfile) entry.getValue())); + } + + return result; + } + + @Override + public Set getOperators() { + Set result = new HashSet(); + + for (UserListEntry entry : playerList.getOppedPlayers().getValuesCB()) { + result.add(getOfflinePlayer((GameProfile) entry.getValue())); + } + + return result; + } + + @Override + public void reloadWhitelist() { + playerList.reloadWhitelist(); + } + + @Override + public GameMode getDefaultGameMode() { + return GameMode.getByValue(console.worlds[0].getWorldInfo().getGameType().getID()); + } + + @Override + public void setDefaultGameMode(GameMode mode) { + Validate.notNull(mode, "Mode cannot be null"); + + for (World world : getWorlds()) { + ((CraftWorld) world).getHandle().worldInfo.setGameType(GameType.getByID(mode.getValue())); + } + } + + @Override + public ConsoleCommandSender getConsoleSender() { + return console.console; + } + + public EntityMetadataStore getEntityMetadata() { + return entityMetadata; + } + + public PlayerMetadataStore getPlayerMetadata() { + return playerMetadata; + } + + public WorldMetadataStore getWorldMetadata() { + return worldMetadata; + } + + @Override + public File getWorldContainer() { + // Cauldron start - return the proper container + if (DimensionManager.getWorld(0) != null) { + return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getWorldDirectory(); + } + // Cauldron end + if (this.getServer().anvilFile != null) { + return this.getServer().anvilFile; + } + + if (container == null) { + container = new File(configuration.getString("settings.world-container", ".")); + } + + return container; + } + + @Override + public OfflinePlayer[] getOfflinePlayers() { + SaveHandler storage = (SaveHandler) console.worlds[0].getSaveHandler(); + String[] files = storage.getPlayerDir().list(new DatFileFilter()); + Set players = new HashSet(); + + for (String file : files) { + try { + players.add(getOfflinePlayer(UUID.fromString(file.substring(0, file.length() - 4)))); + } catch (IllegalArgumentException ex) { + // Who knows what is in this directory, just ignore invalid files + } + } + + players.addAll(getOnlinePlayers()); + + return players.toArray(new OfflinePlayer[players.size()]); + } + + @Override + public Messenger getMessenger() { + return messenger; + } + + @Override + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + StandardMessenger.validatePluginMessage(getMessenger(), source, channel, message); + + for (Player player : getOnlinePlayers()) { + player.sendPluginMessage(source, channel, message); + } + } + + @Override + public Set getListeningPluginChannels() { + Set result = new HashSet(); + + for (Player player : getOnlinePlayers()) { + result.addAll(player.getListeningPluginChannels()); + } + + return result; + } + + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type) { + // TODO: Create the appropriate type, rather than Custom? + return new CraftInventoryCustom(owner, type); + } + + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + return new CraftInventoryCustom(owner, type, title); + } + + @Override + public Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException { + Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!"); + return new CraftInventoryCustom(owner, size); + } + + @Override + public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException { + Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!"); + return new CraftInventoryCustom(owner, size, title); + } + + @Override + public Merchant createMerchant(String title) { + return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); + } + + @Override + public HelpMap getHelpMap() { + return helpMap; + } + + @Override // Paper - add override + public SimpleCommandMap getCommandMap() { + return commandMap; + } + + // Cauldron start + public CraftSimpleCommandMap getCraftCommandMap() { + return craftCommandMap; + } + // Cauldron end + + @Override + public int getMonsterSpawnLimit() { + return monsterSpawn; + } + + @Override + public int getAnimalSpawnLimit() { + return animalSpawn; + } + + @Override + public int getWaterAnimalSpawnLimit() { + return waterAnimalSpawn; + } + + @Override + public int getAmbientSpawnLimit() { + return ambientSpawn; + } + + @Override + public boolean isPrimaryThread() { + return Thread.currentThread().equals(console.primaryThread); + } + + @Override + public String getMotd() { + return console.getMOTD(); + } + + @Override + public WarningState getWarningState() { + return warningState; + } + + public List tabComplete(net.minecraft.command.ICommandSender sender, String message, BlockPos pos, boolean forceCommand) { + // Spigot Start + if ((org.spigotmc.SpigotConfig.tabComplete < 0 || message.length() <= org.spigotmc.SpigotConfig.tabComplete) && !message.contains(" ")) { + return ImmutableList.of(); + } + // Spigot End + if (!(sender instanceof EntityPlayerMP)) { + return ImmutableList.of(); + } + + List offers; + Player player = ((EntityPlayerMP) sender).getBukkitEntity(); + if (message.startsWith("/") || forceCommand) { + offers = tabCompleteCommand(player, message, pos); + } else { + offers = tabCompleteChat(player, message); + } + + TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); + getPluginManager().callEvent(tabEvent); + + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); + } + + public List tabCompleteCommand(Player player, String message, BlockPos pos) { + List completions = null; + try { + if (message.startsWith("/")) { + // Trim leading '/' if present (won't always be present in command blocks) + message = message.substring(1); + } + if (pos == null) { + completions = getCommandMap().tabComplete(player, message); + } else { + completions = getCommandMap().tabComplete(player, message, + new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ())); + } + } catch (CommandException ex) { + player.sendMessage( + ChatColor.RED + "An internal error occurred while attempting to tab-complete this command"); + getLogger().log(Level.SEVERE, + "Exception when " + player.getName() + " attempted to tab complete " + message, ex); + } + + return completions == null ? ImmutableList.of() : completions; + } + + public List tabCompleteChat(Player player, String message) { + List completions = new ArrayList(); + PlayerChatTabCompleteEvent event = new PlayerChatTabCompleteEvent(player, message, completions); + String token = event.getLastToken(); + for (Player p : getOnlinePlayers()) { + if (player.canSee(p) && StringUtil.startsWithIgnoreCase(p.getName(), token)) { + completions.add(p.getName()); + } + } + pluginManager.callEvent(event); + + Iterator it = completions.iterator(); + while (it.hasNext()) { + Object current = it.next(); + if (!(current instanceof String)) { + // Sanity + it.remove(); + } + } + Collections.sort(completions, String.CASE_INSENSITIVE_ORDER); + return completions; + } + + @Override + public CraftItemFactory getItemFactory() { + return CraftItemFactory.instance(); + } + + @Override + public CraftScoreboardManager getScoreboardManager() { + return scoreboardManager; + } + + public void checkSaveState() { + if (this.playerCommandState || this.printSaveWarning || this.console.autosavePeriod <= 0) { + return; + } + this.printSaveWarning = true; + getLogger().log(Level.WARNING, + "A manual (plugin-induced) save has been detected while server is configured to auto-save. This may affect performance.", + warningState == WarningState.ON ? new Throwable() : null); + } + + @Override + public CraftIconCache getServerIcon() { + return icon; + } + + @Override + public CraftIconCache loadServerIcon(File file) throws Exception { + Validate.notNull(file, "File cannot be null"); + if (!file.isFile()) { + throw new IllegalArgumentException(file + " is not a file"); + } + return loadServerIcon0(file); + } + + static CraftIconCache loadServerIcon0(File file) throws Exception { + return loadServerIcon0(ImageIO.read(file)); + } + + @Override + public CraftIconCache loadServerIcon(BufferedImage image) throws Exception { + Validate.notNull(image, "Image cannot be null"); + return loadServerIcon0(image); + } + + static CraftIconCache loadServerIcon0(BufferedImage image) throws Exception { + ByteBuf bytebuf = Unpooled.buffer(); + + Validate.isTrue(image.getWidth() == 64, "Must be 64 pixels wide"); + Validate.isTrue(image.getHeight() == 64, "Must be 64 pixels high"); + ImageIO.write(image, "PNG", new ByteBufOutputStream(bytebuf)); + ByteBuf bytebuf1 = Base64.encode(bytebuf); + + return new CraftIconCache("data:image/png;base64," + bytebuf1.toString(Charsets.UTF_8)); + } + + @Override + public void setIdleTimeout(int threshold) { + console.setPlayerIdleTimeout(threshold); + } + + @Override + public int getIdleTimeout() { + return console.getMaxPlayerIdleMinutes(); + } + + @Override + public ChunkGenerator.ChunkData createChunkData(World world) { + return new CraftChunkData(world); + } + + @Override + public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { + return new CraftBossBar(title, color, style, flags); + } + + @Override + public Entity getEntity(UUID uuid) { + Validate.notNull(uuid, "UUID cannot be null"); + net.minecraft.entity.Entity entity = console.getEntityFromUuid(uuid); // PAIL: getEntity + return entity == null ? null : entity.getBukkitEntity(); + } + + @Override + public org.bukkit.advancement.Advancement getAdvancement(NamespacedKey key) { + Preconditions.checkArgument(key != null, "key"); + + Advancement advancement = console.getAdvancementManager().getAdvancement(CraftNamespacedKey.toMinecraft(key)); + return (advancement == null) ? null : advancement.bukkit; + } + + @Override + public Iterator advancementIterator() { + return Iterators + .unmodifiableIterator(Iterators.transform(console.getAdvancementManager().getAdvancements().iterator(), + new Function() { // PAIL: rename + @Override + public org.bukkit.advancement.Advancement apply(Advancement advancement) { + return advancement.bukkit; + } + })); + } + + @Deprecated + @Override + public UnsafeValues getUnsafe() { + return CraftMagicNumbers.INSTANCE; + } + + // Paper - Add getTPS API - Further improve tick loop + @Override + public double[] getTPS() { + return new double[] { + MinecraftServer.getServerCB().tps1.getAverage(), + MinecraftServer.getServerCB().tps5.getAverage(), + MinecraftServer.getServerCB().tps15.getAverage() + }; + } + // Paper end + + private final Spigot spigot = new Spigot() + { + + @Override + public YamlConfiguration getConfig() + { + return org.spigotmc.SpigotConfig.config; + } + + @Override + public void broadcast(BaseComponent component) { + for (Player player : getOnlinePlayers()) { + player.spigot().sendMessage(component); + } + } + + @Override + public void broadcast(BaseComponent... components) { + for (Player player : getOnlinePlayers()) { + player.spigot().sendMessage(components); + } + } + }; + + @Override + public Spigot spigot() { + return spigot; + } + + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return createProfile(uuid, null); + } + + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { + return createProfile(null, name); + } + + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { + Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); + if (player != null) { + return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer)player); + } + return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); + } + // Paper end +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftSound.java b/src/main/java/org/bukkit/craftbukkit/CraftSound.java new file mode 100644 index 00000000..be48f6e8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftSound.java @@ -0,0 +1,579 @@ +package org.bukkit.craftbukkit; + +import com.google.common.base.Preconditions; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import org.apache.commons.lang3.Validate; +import org.bukkit.Sound; + +public enum CraftSound { + + AMBIENT_CAVE("ambient.cave"), + BLOCK_ANVIL_BREAK("block.anvil.break"), + BLOCK_ANVIL_DESTROY("block.anvil.destroy"), + BLOCK_ANVIL_FALL("block.anvil.fall"), + BLOCK_ANVIL_HIT("block.anvil.hit"), + BLOCK_ANVIL_LAND("block.anvil.land"), + BLOCK_ANVIL_PLACE("block.anvil.place"), + BLOCK_ANVIL_STEP("block.anvil.step"), + BLOCK_ANVIL_USE("block.anvil.use"), + BLOCK_BREWING_STAND_BREW("block.brewing_stand.brew"), + BLOCK_CHEST_CLOSE("block.chest.close"), + BLOCK_CHEST_LOCKED("block.chest.locked"), + BLOCK_CHEST_OPEN("block.chest.open"), + BLOCK_CHORUS_FLOWER_DEATH("block.chorus_flower.death"), + BLOCK_CHORUS_FLOWER_GROW("block.chorus_flower.grow"), + BLOCK_CLOTH_BREAK("block.cloth.break"), + BLOCK_CLOTH_FALL("block.cloth.fall"), + BLOCK_CLOTH_HIT("block.cloth.hit"), + BLOCK_CLOTH_PLACE("block.cloth.place"), + BLOCK_CLOTH_STEP("block.cloth.step"), + BLOCK_COMPARATOR_CLICK("block.comparator.click"), + BLOCK_DISPENSER_DISPENSE("block.dispenser.dispense"), + BLOCK_DISPENSER_FAIL("block.dispenser.fail"), + BLOCK_DISPENSER_LAUNCH("block.dispenser.launch"), + BLOCK_ENCHANTMENT_TABLE_USE("block.enchantment_table.use"), + BLOCK_ENDERCHEST_CLOSE("block.enderchest.close"), + BLOCK_ENDERCHEST_OPEN("block.enderchest.open"), + BLOCK_END_GATEWAY_SPAWN("block.end_gateway.spawn"), + BLOCK_END_PORTAL_FRAME_FILL("block.end_portal_frame.fill"), + BLOCK_END_PORTAL_SPAWN("block.end_portal.spawn"), + BLOCK_FENCE_GATE_CLOSE("block.fence_gate.close"), + BLOCK_FENCE_GATE_OPEN("block.fence_gate.open"), + BLOCK_FIRE_AMBIENT("block.fire.ambient"), + BLOCK_FIRE_EXTINGUISH("block.fire.extinguish"), + BLOCK_FURNACE_FIRE_CRACKLE("block.furnace.fire_crackle"), + BLOCK_GLASS_BREAK("block.glass.break"), + BLOCK_GLASS_FALL("block.glass.fall"), + BLOCK_GLASS_HIT("block.glass.hit"), + BLOCK_GLASS_PLACE("block.glass.place"), + BLOCK_GLASS_STEP("block.glass.step"), + BLOCK_GRASS_BREAK("block.grass.break"), + BLOCK_GRASS_FALL("block.grass.fall"), + BLOCK_GRASS_HIT("block.grass.hit"), + BLOCK_GRASS_PLACE("block.grass.place"), + BLOCK_GRASS_STEP("block.grass.step"), + BLOCK_GRAVEL_BREAK("block.gravel.break"), + BLOCK_GRAVEL_FALL("block.gravel.fall"), + BLOCK_GRAVEL_HIT("block.gravel.hit"), + BLOCK_GRAVEL_PLACE("block.gravel.place"), + BLOCK_GRAVEL_STEP("block.gravel.step"), + BLOCK_IRON_DOOR_CLOSE("block.iron_door.close"), + BLOCK_IRON_DOOR_OPEN("block.iron_door.open"), + BLOCK_IRON_TRAPDOOR_CLOSE("block.iron_trapdoor.close"), + BLOCK_IRON_TRAPDOOR_OPEN("block.iron_trapdoor.open"), + BLOCK_LADDER_BREAK("block.ladder.break"), + BLOCK_LADDER_FALL("block.ladder.fall"), + BLOCK_LADDER_HIT("block.ladder.hit"), + BLOCK_LADDER_PLACE("block.ladder.place"), + BLOCK_LADDER_STEP("block.ladder.step"), + BLOCK_LAVA_AMBIENT("block.lava.ambient"), + BLOCK_LAVA_EXTINGUISH("block.lava.extinguish"), + BLOCK_LAVA_POP("block.lava.pop"), + BLOCK_LEVER_CLICK("block.lever.click"), + BLOCK_METAL_BREAK("block.metal.break"), + BLOCK_METAL_FALL("block.metal.fall"), + BLOCK_METAL_HIT("block.metal.hit"), + BLOCK_METAL_PLACE("block.metal.place"), + BLOCK_METAL_PRESSUREPLATE_CLICK_OFF("block.metal_pressureplate.click_off"), + BLOCK_METAL_PRESSUREPLATE_CLICK_ON("block.metal_pressureplate.click_on"), + BLOCK_METAL_STEP("block.metal.step"), + BLOCK_NOTE_BASEDRUM("block.note.basedrum"), + BLOCK_NOTE_BASS("block.note.bass"), + BLOCK_NOTE_BELL("block.note.bell"), + BLOCK_NOTE_CHIME("block.note.chime"), + BLOCK_NOTE_FLUTE("block.note.flute"), + BLOCK_NOTE_GUITAR("block.note.guitar"), + BLOCK_NOTE_HARP("block.note.harp"), + BLOCK_NOTE_HAT("block.note.hat"), + BLOCK_NOTE_PLING("block.note.pling"), + BLOCK_NOTE_SNARE("block.note.snare"), + BLOCK_NOTE_XYLOPHONE("block.note.xylophone"), + BLOCK_PISTON_CONTRACT("block.piston.contract"), + BLOCK_PISTON_EXTEND("block.piston.extend"), + BLOCK_PORTAL_AMBIENT("block.portal.ambient"), + BLOCK_PORTAL_TRAVEL("block.portal.travel"), + BLOCK_PORTAL_TRIGGER("block.portal.trigger"), + BLOCK_REDSTONE_TORCH_BURNOUT("block.redstone_torch.burnout"), + BLOCK_SAND_BREAK("block.sand.break"), + BLOCK_SAND_FALL("block.sand.fall"), + BLOCK_SAND_HIT("block.sand.hit"), + BLOCK_SAND_PLACE("block.sand.place"), + BLOCK_SAND_STEP("block.sand.step"), + BLOCK_SHULKER_BOX_CLOSE("block.shulker_box.close"), + BLOCK_SHULKER_BOX_OPEN("block.shulker_box.open"), + BLOCK_SLIME_BREAK("block.slime.break"), + BLOCK_SLIME_FALL("block.slime.fall"), + BLOCK_SLIME_HIT("block.slime.hit"), + BLOCK_SLIME_PLACE("block.slime.place"), + BLOCK_SLIME_STEP("block.slime.step"), + BLOCK_SNOW_BREAK("block.snow.break"), + BLOCK_SNOW_FALL("block.snow.fall"), + BLOCK_SNOW_HIT("block.snow.hit"), + BLOCK_SNOW_PLACE("block.snow.place"), + BLOCK_SNOW_STEP("block.snow.step"), + BLOCK_STONE_BREAK("block.stone.break"), + BLOCK_STONE_BUTTON_CLICK_OFF("block.stone_button.click_off"), + BLOCK_STONE_BUTTON_CLICK_ON("block.stone_button.click_on"), + BLOCK_STONE_FALL("block.stone.fall"), + BLOCK_STONE_HIT("block.stone.hit"), + BLOCK_STONE_PLACE("block.stone.place"), + BLOCK_STONE_PRESSUREPLATE_CLICK_OFF("block.stone_pressureplate.click_off"), + BLOCK_STONE_PRESSUREPLATE_CLICK_ON("block.stone_pressureplate.click_on"), + BLOCK_STONE_STEP("block.stone.step"), + BLOCK_TRIPWIRE_ATTACH("block.tripwire.attach"), + BLOCK_TRIPWIRE_CLICK_OFF("block.tripwire.click_off"), + BLOCK_TRIPWIRE_CLICK_ON("block.tripwire.click_on"), + BLOCK_TRIPWIRE_DETACH("block.tripwire.detach"), + BLOCK_WATERLILY_PLACE("block.waterlily.place"), + BLOCK_WATER_AMBIENT("block.water.ambient"), + BLOCK_WOODEN_DOOR_CLOSE("block.wooden_door.close"), + BLOCK_WOODEN_DOOR_OPEN("block.wooden_door.open"), + BLOCK_WOODEN_TRAPDOOR_CLOSE("block.wooden_trapdoor.close"), + BLOCK_WOODEN_TRAPDOOR_OPEN("block.wooden_trapdoor.open"), + BLOCK_WOOD_BREAK("block.wood.break"), + BLOCK_WOOD_BUTTON_CLICK_OFF("block.wood_button.click_off"), + BLOCK_WOOD_BUTTON_CLICK_ON("block.wood_button.click_on"), + BLOCK_WOOD_FALL("block.wood.fall"), + BLOCK_WOOD_HIT("block.wood.hit"), + BLOCK_WOOD_PLACE("block.wood.place"), + BLOCK_WOOD_PRESSUREPLATE_CLICK_OFF("block.wood_pressureplate.click_off"), + BLOCK_WOOD_PRESSUREPLATE_CLICK_ON("block.wood_pressureplate.click_on"), + BLOCK_WOOD_STEP("block.wood.step"), + ENCHANT_THORNS_HIT("enchant.thorns.hit"), + ENTITY_ARMORSTAND_BREAK("entity.armorstand.break"), + ENTITY_ARMORSTAND_FALL("entity.armorstand.fall"), + ENTITY_ARMORSTAND_HIT("entity.armorstand.hit"), + ENTITY_ARMORSTAND_PLACE("entity.armorstand.place"), + ENTITY_ARROW_HIT("entity.arrow.hit"), + ENTITY_ARROW_HIT_PLAYER("entity.arrow.hit_player"), + ENTITY_ARROW_SHOOT("entity.arrow.shoot"), + ENTITY_BAT_AMBIENT("entity.bat.ambient"), + ENTITY_BAT_DEATH("entity.bat.death"), + ENTITY_BAT_HURT("entity.bat.hurt"), + ENTITY_BAT_LOOP("entity.bat.loop"), + ENTITY_BAT_TAKEOFF("entity.bat.takeoff"), + ENTITY_BLAZE_AMBIENT("entity.blaze.ambient"), + ENTITY_BLAZE_BURN("entity.blaze.burn"), + ENTITY_BLAZE_DEATH("entity.blaze.death"), + ENTITY_BLAZE_HURT("entity.blaze.hurt"), + ENTITY_BLAZE_SHOOT("entity.blaze.shoot"), + ENTITY_BOAT_PADDLE_LAND("entity.boat.paddle_land"), + ENTITY_BOAT_PADDLE_WATER("entity.boat.paddle_water"), + ENTITY_BOBBER_RETRIEVE("entity.bobber.retrieve"), + ENTITY_BOBBER_SPLASH("entity.bobber.splash"), + ENTITY_BOBBER_THROW("entity.bobber.throw"), + ENTITY_CAT_AMBIENT("entity.cat.ambient"), + ENTITY_CAT_DEATH("entity.cat.death"), + ENTITY_CAT_HISS("entity.cat.hiss"), + ENTITY_CAT_HURT("entity.cat.hurt"), + ENTITY_CAT_PURR("entity.cat.purr"), + ENTITY_CAT_PURREOW("entity.cat.purreow"), + ENTITY_CHICKEN_AMBIENT("entity.chicken.ambient"), + ENTITY_CHICKEN_DEATH("entity.chicken.death"), + ENTITY_CHICKEN_EGG("entity.chicken.egg"), + ENTITY_CHICKEN_HURT("entity.chicken.hurt"), + ENTITY_CHICKEN_STEP("entity.chicken.step"), + ENTITY_COW_AMBIENT("entity.cow.ambient"), + ENTITY_COW_DEATH("entity.cow.death"), + ENTITY_COW_HURT("entity.cow.hurt"), + ENTITY_COW_MILK("entity.cow.milk"), + ENTITY_COW_STEP("entity.cow.step"), + ENTITY_CREEPER_DEATH("entity.creeper.death"), + ENTITY_CREEPER_HURT("entity.creeper.hurt"), + ENTITY_CREEPER_PRIMED("entity.creeper.primed"), + ENTITY_DONKEY_AMBIENT("entity.donkey.ambient"), + ENTITY_DONKEY_ANGRY("entity.donkey.angry"), + ENTITY_DONKEY_CHEST("entity.donkey.chest"), + ENTITY_DONKEY_DEATH("entity.donkey.death"), + ENTITY_DONKEY_HURT("entity.donkey.hurt"), + ENTITY_EGG_THROW("entity.egg.throw"), + ENTITY_ELDER_GUARDIAN_AMBIENT("entity.elder_guardian.ambient"), + ENTITY_ELDER_GUARDIAN_AMBIENT_LAND("entity.elder_guardian.ambient_land"), + ENTITY_ELDER_GUARDIAN_CURSE("entity.elder_guardian.curse"), + ENTITY_ELDER_GUARDIAN_DEATH("entity.elder_guardian.death"), + ENTITY_ELDER_GUARDIAN_DEATH_LAND("entity.elder_guardian.death_land"), + ENTITY_ELDER_GUARDIAN_FLOP("entity.elder_guardian.flop"), + ENTITY_ELDER_GUARDIAN_HURT("entity.elder_guardian.hurt"), + ENTITY_ELDER_GUARDIAN_HURT_LAND("entity.elder_guardian.hurt_land"), + ENTITY_ENDERDRAGON_AMBIENT("entity.enderdragon.ambient"), + ENTITY_ENDERDRAGON_DEATH("entity.enderdragon.death"), + ENTITY_ENDERDRAGON_FIREBALL_EXPLODE("entity.enderdragon_fireball.explode"), + ENTITY_ENDERDRAGON_FLAP("entity.enderdragon.flap"), + ENTITY_ENDERDRAGON_GROWL("entity.enderdragon.growl"), + ENTITY_ENDERDRAGON_HURT("entity.enderdragon.hurt"), + ENTITY_ENDERDRAGON_SHOOT("entity.enderdragon.shoot"), + ENTITY_ENDEREYE_DEATH("entity.endereye.death"), + ENTITY_ENDEREYE_LAUNCH("entity.endereye.launch"), + ENTITY_ENDERMEN_AMBIENT("entity.endermen.ambient"), + ENTITY_ENDERMEN_DEATH("entity.endermen.death"), + ENTITY_ENDERMEN_HURT("entity.endermen.hurt"), + ENTITY_ENDERMEN_SCREAM("entity.endermen.scream"), + ENTITY_ENDERMEN_STARE("entity.endermen.stare"), + ENTITY_ENDERMEN_TELEPORT("entity.endermen.teleport"), + ENTITY_ENDERMITE_AMBIENT("entity.endermite.ambient"), + ENTITY_ENDERMITE_DEATH("entity.endermite.death"), + ENTITY_ENDERMITE_HURT("entity.endermite.hurt"), + ENTITY_ENDERMITE_STEP("entity.endermite.step"), + ENTITY_ENDERPEARL_THROW("entity.enderpearl.throw"), + ENTITY_EVOCATION_FANGS_ATTACK("entity.evocation_fangs.attack"), + ENTITY_EVOCATION_ILLAGER_AMBIENT("entity.evocation_illager.ambient"), + ENTITY_EVOCATION_ILLAGER_CAST_SPELL("entity.evocation_illager.cast_spell"), + ENTITY_EVOCATION_ILLAGER_DEATH("entity.evocation_illager.death"), + ENTITY_EVOCATION_ILLAGER_HURT("entity.evocation_illager.hurt"), + ENTITY_EVOCATION_ILLAGER_PREPARE_ATTACK("entity.evocation_illager.prepare_attack"), + ENTITY_EVOCATION_ILLAGER_PREPARE_SUMMON("entity.evocation_illager.prepare_summon"), + ENTITY_EVOCATION_ILLAGER_PREPARE_WOLOLO("entity.evocation_illager.prepare_wololo"), + ENTITY_EXPERIENCE_BOTTLE_THROW("entity.experience_bottle.throw"), + ENTITY_EXPERIENCE_ORB_PICKUP("entity.experience_orb.pickup"), + ENTITY_FIREWORK_BLAST("entity.firework.blast"), + ENTITY_FIREWORK_BLAST_FAR("entity.firework.blast_far"), + ENTITY_FIREWORK_LARGE_BLAST("entity.firework.large_blast"), + ENTITY_FIREWORK_LARGE_BLAST_FAR("entity.firework.large_blast_far"), + ENTITY_FIREWORK_LAUNCH("entity.firework.launch"), + ENTITY_FIREWORK_SHOOT("entity.firework.shoot"), + ENTITY_FIREWORK_TWINKLE("entity.firework.twinkle"), + ENTITY_FIREWORK_TWINKLE_FAR("entity.firework.twinkle_far"), + ENTITY_GENERIC_BIG_FALL("entity.generic.big_fall"), + ENTITY_GENERIC_BURN("entity.generic.burn"), + ENTITY_GENERIC_DEATH("entity.generic.death"), + ENTITY_GENERIC_DRINK("entity.generic.drink"), + ENTITY_GENERIC_EAT("entity.generic.eat"), + ENTITY_GENERIC_EXPLODE("entity.generic.explode"), + ENTITY_GENERIC_EXTINGUISH_FIRE("entity.generic.extinguish_fire"), + ENTITY_GENERIC_HURT("entity.generic.hurt"), + ENTITY_GENERIC_SMALL_FALL("entity.generic.small_fall"), + ENTITY_GENERIC_SPLASH("entity.generic.splash"), + ENTITY_GENERIC_SWIM("entity.generic.swim"), + ENTITY_GHAST_AMBIENT("entity.ghast.ambient"), + ENTITY_GHAST_DEATH("entity.ghast.death"), + ENTITY_GHAST_HURT("entity.ghast.hurt"), + ENTITY_GHAST_SCREAM("entity.ghast.scream"), + ENTITY_GHAST_SHOOT("entity.ghast.shoot"), + ENTITY_GHAST_WARN("entity.ghast.warn"), + ENTITY_GUARDIAN_AMBIENT("entity.guardian.ambient"), + ENTITY_GUARDIAN_AMBIENT_LAND("entity.guardian.ambient_land"), + ENTITY_GUARDIAN_ATTACK("entity.guardian.attack"), + ENTITY_GUARDIAN_DEATH("entity.guardian.death"), + ENTITY_GUARDIAN_DEATH_LAND("entity.guardian.death_land"), + ENTITY_GUARDIAN_FLOP("entity.guardian.flop"), + ENTITY_GUARDIAN_HURT("entity.guardian.hurt"), + ENTITY_GUARDIAN_HURT_LAND("entity.guardian.hurt_land"), + ENTITY_HORSE_AMBIENT("entity.horse.ambient"), + ENTITY_HORSE_ANGRY("entity.horse.angry"), + ENTITY_HORSE_ARMOR("entity.horse.armor"), + ENTITY_HORSE_BREATHE("entity.horse.breathe"), + ENTITY_HORSE_DEATH("entity.horse.death"), + ENTITY_HORSE_EAT("entity.horse.eat"), + ENTITY_HORSE_GALLOP("entity.horse.gallop"), + ENTITY_HORSE_HURT("entity.horse.hurt"), + ENTITY_HORSE_JUMP("entity.horse.jump"), + ENTITY_HORSE_LAND("entity.horse.land"), + ENTITY_HORSE_SADDLE("entity.horse.saddle"), + ENTITY_HORSE_STEP("entity.horse.step"), + ENTITY_HORSE_STEP_WOOD("entity.horse.step_wood"), + ENTITY_HOSTILE_BIG_FALL("entity.hostile.big_fall"), + ENTITY_HOSTILE_DEATH("entity.hostile.death"), + ENTITY_HOSTILE_HURT("entity.hostile.hurt"), + ENTITY_HOSTILE_SMALL_FALL("entity.hostile.small_fall"), + ENTITY_HOSTILE_SPLASH("entity.hostile.splash"), + ENTITY_HOSTILE_SWIM("entity.hostile.swim"), + ENTITY_HUSK_AMBIENT("entity.husk.ambient"), + ENTITY_HUSK_DEATH("entity.husk.death"), + ENTITY_HUSK_HURT("entity.husk.hurt"), + ENTITY_HUSK_STEP("entity.husk.step"), + ENTITY_ILLUSION_ILLAGER_AMBIENT("entity.illusion_illager.ambient"), + ENTITY_ILLUSION_ILLAGER_CAST_SPELL("entity.illusion_illager.cast_spell"), + ENTITY_ILLUSION_ILLAGER_DEATH("entity.illusion_illager.death"), + ENTITY_ILLUSION_ILLAGER_HURT("entity.illusion_illager.hurt"), + ENTITY_ILLUSION_ILLAGER_MIRROR_MOVE("entity.illusion_illager.mirror_move"), + ENTITY_ILLUSION_ILLAGER_PREPARE_BLINDNESS("entity.illusion_illager.prepare_blindness"), + ENTITY_ILLUSION_ILLAGER_PREPARE_MIRROR("entity.illusion_illager.prepare_mirror"), + ENTITY_IRONGOLEM_ATTACK("entity.irongolem.attack"), + ENTITY_IRONGOLEM_DEATH("entity.irongolem.death"), + ENTITY_IRONGOLEM_HURT("entity.irongolem.hurt"), + ENTITY_IRONGOLEM_STEP("entity.irongolem.step"), + ENTITY_ITEMFRAME_ADD_ITEM("entity.itemframe.add_item"), + ENTITY_ITEMFRAME_BREAK("entity.itemframe.break"), + ENTITY_ITEMFRAME_PLACE("entity.itemframe.place"), + ENTITY_ITEMFRAME_REMOVE_ITEM("entity.itemframe.remove_item"), + ENTITY_ITEMFRAME_ROTATE_ITEM("entity.itemframe.rotate_item"), + ENTITY_ITEM_BREAK("entity.item.break"), + ENTITY_ITEM_PICKUP("entity.item.pickup"), + ENTITY_LEASHKNOT_BREAK("entity.leashknot.break"), + ENTITY_LEASHKNOT_PLACE("entity.leashknot.place"), + ENTITY_LIGHTNING_IMPACT("entity.lightning.impact"), + ENTITY_LIGHTNING_THUNDER("entity.lightning.thunder"), + ENTITY_LINGERINGPOTION_THROW("entity.lingeringpotion.throw"), + ENTITY_LLAMA_AMBIENT("entity.llama.ambient"), + ENTITY_LLAMA_ANGRY("entity.llama.angry"), + ENTITY_LLAMA_CHEST("entity.llama.chest"), + ENTITY_LLAMA_DEATH("entity.llama.death"), + ENTITY_LLAMA_EAT("entity.llama.eat"), + ENTITY_LLAMA_HURT("entity.llama.hurt"), + ENTITY_LLAMA_SPIT("entity.llama.spit"), + ENTITY_LLAMA_STEP("entity.llama.step"), + ENTITY_LLAMA_SWAG("entity.llama.swag"), + ENTITY_MAGMACUBE_DEATH("entity.magmacube.death"), + ENTITY_MAGMACUBE_HURT("entity.magmacube.hurt"), + ENTITY_MAGMACUBE_JUMP("entity.magmacube.jump"), + ENTITY_MAGMACUBE_SQUISH("entity.magmacube.squish"), + ENTITY_MINECART_INSIDE("entity.minecart.inside"), + ENTITY_MINECART_RIDING("entity.minecart.riding"), + ENTITY_MOOSHROOM_SHEAR("entity.mooshroom.shear"), + ENTITY_MULE_AMBIENT("entity.mule.ambient"), + ENTITY_MULE_CHEST("entity.mule.chest"), + ENTITY_MULE_DEATH("entity.mule.death"), + ENTITY_MULE_HURT("entity.mule.hurt"), + ENTITY_PAINTING_BREAK("entity.painting.break"), + ENTITY_PAINTING_PLACE("entity.painting.place"), + ENTITY_PARROT_AMBIENT("entity.parrot.ambient"), + ENTITY_PARROT_DEATH("entity.parrot.death"), + ENTITY_PARROT_EAT("entity.parrot.eat"), + ENTITY_PARROT_FLY("entity.parrot.fly"), + ENTITY_PARROT_HURT("entity.parrot.hurt"), + ENTITY_PARROT_IMITATE_BLAZE("entity.parrot.imitate.blaze"), + ENTITY_PARROT_IMITATE_CREEPER("entity.parrot.imitate.creeper"), + ENTITY_PARROT_IMITATE_ELDER_GUARDIAN("entity.parrot.imitate.elder_guardian"), + ENTITY_PARROT_IMITATE_ENDERDRAGON("entity.parrot.imitate.enderdragon"), + ENTITY_PARROT_IMITATE_ENDERMAN("entity.parrot.imitate.enderman"), + ENTITY_PARROT_IMITATE_ENDERMITE("entity.parrot.imitate.endermite"), + ENTITY_PARROT_IMITATE_EVOCATION_ILLAGER("entity.parrot.imitate.evocation_illager"), + ENTITY_PARROT_IMITATE_GHAST("entity.parrot.imitate.ghast"), + ENTITY_PARROT_IMITATE_HUSK("entity.parrot.imitate.husk"), + ENTITY_PARROT_IMITATE_ILLUSION_ILLAGER("entity.parrot.imitate.illusion_illager"), + ENTITY_PARROT_IMITATE_MAGMACUBE("entity.parrot.imitate.magmacube"), + ENTITY_PARROT_IMITATE_POLAR_BEAR("entity.parrot.imitate.polar_bear"), + ENTITY_PARROT_IMITATE_SHULKER("entity.parrot.imitate.shulker"), + ENTITY_PARROT_IMITATE_SILVERFISH("entity.parrot.imitate.silverfish"), + ENTITY_PARROT_IMITATE_SKELETON("entity.parrot.imitate.skeleton"), + ENTITY_PARROT_IMITATE_SLIME("entity.parrot.imitate.slime"), + ENTITY_PARROT_IMITATE_SPIDER("entity.parrot.imitate.spider"), + ENTITY_PARROT_IMITATE_STRAY("entity.parrot.imitate.stray"), + ENTITY_PARROT_IMITATE_VEX("entity.parrot.imitate.vex"), + ENTITY_PARROT_IMITATE_VINDICATION_ILLAGER("entity.parrot.imitate.vindication_illager"), + ENTITY_PARROT_IMITATE_WITCH("entity.parrot.imitate.witch"), + ENTITY_PARROT_IMITATE_WITHER("entity.parrot.imitate.wither"), + ENTITY_PARROT_IMITATE_WITHER_SKELETON("entity.parrot.imitate.wither_skeleton"), + ENTITY_PARROT_IMITATE_WOLF("entity.parrot.imitate.wolf"), + ENTITY_PARROT_IMITATE_ZOMBIE("entity.parrot.imitate.zombie"), + ENTITY_PARROT_IMITATE_ZOMBIE_PIGMAN("entity.parrot.imitate.zombie_pigman"), + ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER("entity.parrot.imitate.zombie_villager"), + ENTITY_PARROT_STEP("entity.parrot.step"), + ENTITY_PIG_AMBIENT("entity.pig.ambient"), + ENTITY_PIG_DEATH("entity.pig.death"), + ENTITY_PIG_HURT("entity.pig.hurt"), + ENTITY_PIG_SADDLE("entity.pig.saddle"), + ENTITY_PIG_STEP("entity.pig.step"), + ENTITY_PLAYER_ATTACK_CRIT("entity.player.attack.crit"), + ENTITY_PLAYER_ATTACK_KNOCKBACK("entity.player.attack.knockback"), + ENTITY_PLAYER_ATTACK_NODAMAGE("entity.player.attack.nodamage"), + ENTITY_PLAYER_ATTACK_STRONG("entity.player.attack.strong"), + ENTITY_PLAYER_ATTACK_SWEEP("entity.player.attack.sweep"), + ENTITY_PLAYER_ATTACK_WEAK("entity.player.attack.weak"), + ENTITY_PLAYER_BIG_FALL("entity.player.big_fall"), + ENTITY_PLAYER_BREATH("entity.player.breath"), + ENTITY_PLAYER_BURP("entity.player.burp"), + ENTITY_PLAYER_DEATH("entity.player.death"), + ENTITY_PLAYER_HURT("entity.player.hurt"), + ENTITY_PLAYER_HURT_DROWN("entity.player.hurt_drown"), + ENTITY_PLAYER_HURT_ON_FIRE("entity.player.hurt_on_fire"), + ENTITY_PLAYER_LEVELUP("entity.player.levelup"), + ENTITY_PLAYER_SMALL_FALL("entity.player.small_fall"), + ENTITY_PLAYER_SPLASH("entity.player.splash"), + ENTITY_PLAYER_SWIM("entity.player.swim"), + ENTITY_POLAR_BEAR_AMBIENT("entity.polar_bear.ambient"), + ENTITY_POLAR_BEAR_BABY_AMBIENT("entity.polar_bear.baby_ambient"), + ENTITY_POLAR_BEAR_DEATH("entity.polar_bear.death"), + ENTITY_POLAR_BEAR_HURT("entity.polar_bear.hurt"), + ENTITY_POLAR_BEAR_STEP("entity.polar_bear.step"), + ENTITY_POLAR_BEAR_WARNING("entity.polar_bear.warning"), + ENTITY_RABBIT_AMBIENT("entity.rabbit.ambient"), + ENTITY_RABBIT_ATTACK("entity.rabbit.attack"), + ENTITY_RABBIT_DEATH("entity.rabbit.death"), + ENTITY_RABBIT_HURT("entity.rabbit.hurt"), + ENTITY_RABBIT_JUMP("entity.rabbit.jump"), + ENTITY_SHEEP_AMBIENT("entity.sheep.ambient"), + ENTITY_SHEEP_DEATH("entity.sheep.death"), + ENTITY_SHEEP_HURT("entity.sheep.hurt"), + ENTITY_SHEEP_SHEAR("entity.sheep.shear"), + ENTITY_SHEEP_STEP("entity.sheep.step"), + ENTITY_SHULKER_AMBIENT("entity.shulker.ambient"), + ENTITY_SHULKER_BULLET_HIT("entity.shulker_bullet.hit"), + ENTITY_SHULKER_BULLET_HURT("entity.shulker_bullet.hurt"), + ENTITY_SHULKER_CLOSE("entity.shulker.close"), + ENTITY_SHULKER_DEATH("entity.shulker.death"), + ENTITY_SHULKER_HURT("entity.shulker.hurt"), + ENTITY_SHULKER_HURT_CLOSED("entity.shulker.hurt_closed"), + ENTITY_SHULKER_OPEN("entity.shulker.open"), + ENTITY_SHULKER_SHOOT("entity.shulker.shoot"), + ENTITY_SHULKER_TELEPORT("entity.shulker.teleport"), + ENTITY_SILVERFISH_AMBIENT("entity.silverfish.ambient"), + ENTITY_SILVERFISH_DEATH("entity.silverfish.death"), + ENTITY_SILVERFISH_HURT("entity.silverfish.hurt"), + ENTITY_SILVERFISH_STEP("entity.silverfish.step"), + ENTITY_SKELETON_AMBIENT("entity.skeleton.ambient"), + ENTITY_SKELETON_DEATH("entity.skeleton.death"), + ENTITY_SKELETON_HORSE_AMBIENT("entity.skeleton_horse.ambient"), + ENTITY_SKELETON_HORSE_DEATH("entity.skeleton_horse.death"), + ENTITY_SKELETON_HORSE_HURT("entity.skeleton_horse.hurt"), + ENTITY_SKELETON_HURT("entity.skeleton.hurt"), + ENTITY_SKELETON_SHOOT("entity.skeleton.shoot"), + ENTITY_SKELETON_STEP("entity.skeleton.step"), + ENTITY_SLIME_ATTACK("entity.slime.attack"), + ENTITY_SLIME_DEATH("entity.slime.death"), + ENTITY_SLIME_HURT("entity.slime.hurt"), + ENTITY_SLIME_JUMP("entity.slime.jump"), + ENTITY_SLIME_SQUISH("entity.slime.squish"), + ENTITY_SMALL_MAGMACUBE_DEATH("entity.small_magmacube.death"), + ENTITY_SMALL_MAGMACUBE_HURT("entity.small_magmacube.hurt"), + ENTITY_SMALL_MAGMACUBE_SQUISH("entity.small_magmacube.squish"), + ENTITY_SMALL_SLIME_DEATH("entity.small_slime.death"), + ENTITY_SMALL_SLIME_HURT("entity.small_slime.hurt"), + ENTITY_SMALL_SLIME_JUMP("entity.small_slime.jump"), + ENTITY_SMALL_SLIME_SQUISH("entity.small_slime.squish"), + ENTITY_SNOWBALL_THROW("entity.snowball.throw"), + ENTITY_SNOWMAN_AMBIENT("entity.snowman.ambient"), + ENTITY_SNOWMAN_DEATH("entity.snowman.death"), + ENTITY_SNOWMAN_HURT("entity.snowman.hurt"), + ENTITY_SNOWMAN_SHOOT("entity.snowman.shoot"), + ENTITY_SPIDER_AMBIENT("entity.spider.ambient"), + ENTITY_SPIDER_DEATH("entity.spider.death"), + ENTITY_SPIDER_HURT("entity.spider.hurt"), + ENTITY_SPIDER_STEP("entity.spider.step"), + ENTITY_SPLASH_POTION_BREAK("entity.splash_potion.break"), + ENTITY_SPLASH_POTION_THROW("entity.splash_potion.throw"), + ENTITY_SQUID_AMBIENT("entity.squid.ambient"), + ENTITY_SQUID_DEATH("entity.squid.death"), + ENTITY_SQUID_HURT("entity.squid.hurt"), + ENTITY_STRAY_AMBIENT("entity.stray.ambient"), + ENTITY_STRAY_DEATH("entity.stray.death"), + ENTITY_STRAY_HURT("entity.stray.hurt"), + ENTITY_STRAY_STEP("entity.stray.step"), + ENTITY_TNT_PRIMED("entity.tnt.primed"), + ENTITY_VEX_AMBIENT("entity.vex.ambient"), + ENTITY_VEX_CHARGE("entity.vex.charge"), + ENTITY_VEX_DEATH("entity.vex.death"), + ENTITY_VEX_HURT("entity.vex.hurt"), + ENTITY_VILLAGER_AMBIENT("entity.villager.ambient"), + ENTITY_VILLAGER_DEATH("entity.villager.death"), + ENTITY_VILLAGER_HURT("entity.villager.hurt"), + ENTITY_VILLAGER_NO("entity.villager.no"), + ENTITY_VILLAGER_TRADING("entity.villager.trading"), + ENTITY_VILLAGER_YES("entity.villager.yes"), + ENTITY_VINDICATION_ILLAGER_AMBIENT("entity.vindication_illager.ambient"), + ENTITY_VINDICATION_ILLAGER_DEATH("entity.vindication_illager.death"), + ENTITY_VINDICATION_ILLAGER_HURT("entity.vindication_illager.hurt"), + ENTITY_WITCH_AMBIENT("entity.witch.ambient"), + ENTITY_WITCH_DEATH("entity.witch.death"), + ENTITY_WITCH_DRINK("entity.witch.drink"), + ENTITY_WITCH_HURT("entity.witch.hurt"), + ENTITY_WITCH_THROW("entity.witch.throw"), + ENTITY_WITHER_AMBIENT("entity.wither.ambient"), + ENTITY_WITHER_BREAK_BLOCK("entity.wither.break_block"), + ENTITY_WITHER_DEATH("entity.wither.death"), + ENTITY_WITHER_HURT("entity.wither.hurt"), + ENTITY_WITHER_SHOOT("entity.wither.shoot"), + ENTITY_WITHER_SKELETON_AMBIENT("entity.wither_skeleton.ambient"), + ENTITY_WITHER_SKELETON_DEATH("entity.wither_skeleton.death"), + ENTITY_WITHER_SKELETON_HURT("entity.wither_skeleton.hurt"), + ENTITY_WITHER_SKELETON_STEP("entity.wither_skeleton.step"), + ENTITY_WITHER_SPAWN("entity.wither.spawn"), + ENTITY_WOLF_AMBIENT("entity.wolf.ambient"), + ENTITY_WOLF_DEATH("entity.wolf.death"), + ENTITY_WOLF_GROWL("entity.wolf.growl"), + ENTITY_WOLF_HOWL("entity.wolf.howl"), + ENTITY_WOLF_HURT("entity.wolf.hurt"), + ENTITY_WOLF_PANT("entity.wolf.pant"), + ENTITY_WOLF_SHAKE("entity.wolf.shake"), + ENTITY_WOLF_STEP("entity.wolf.step"), + ENTITY_WOLF_WHINE("entity.wolf.whine"), + ENTITY_ZOMBIE_AMBIENT("entity.zombie.ambient"), + ENTITY_ZOMBIE_ATTACK_DOOR_WOOD("entity.zombie.attack_door_wood"), + ENTITY_ZOMBIE_ATTACK_IRON_DOOR("entity.zombie.attack_iron_door"), + ENTITY_ZOMBIE_BREAK_DOOR_WOOD("entity.zombie.break_door_wood"), + ENTITY_ZOMBIE_DEATH("entity.zombie.death"), + ENTITY_ZOMBIE_HORSE_AMBIENT("entity.zombie_horse.ambient"), + ENTITY_ZOMBIE_HORSE_DEATH("entity.zombie_horse.death"), + ENTITY_ZOMBIE_HORSE_HURT("entity.zombie_horse.hurt"), + ENTITY_ZOMBIE_HURT("entity.zombie.hurt"), + ENTITY_ZOMBIE_INFECT("entity.zombie.infect"), + ENTITY_ZOMBIE_PIG_AMBIENT("entity.zombie_pig.ambient"), + ENTITY_ZOMBIE_PIG_ANGRY("entity.zombie_pig.angry"), + ENTITY_ZOMBIE_PIG_DEATH("entity.zombie_pig.death"), + ENTITY_ZOMBIE_PIG_HURT("entity.zombie_pig.hurt"), + ENTITY_ZOMBIE_STEP("entity.zombie.step"), + ENTITY_ZOMBIE_VILLAGER_AMBIENT("entity.zombie_villager.ambient"), + ENTITY_ZOMBIE_VILLAGER_CONVERTED("entity.zombie_villager.converted"), + ENTITY_ZOMBIE_VILLAGER_CURE("entity.zombie_villager.cure"), + ENTITY_ZOMBIE_VILLAGER_DEATH("entity.zombie_villager.death"), + ENTITY_ZOMBIE_VILLAGER_HURT("entity.zombie_villager.hurt"), + ENTITY_ZOMBIE_VILLAGER_STEP("entity.zombie_villager.step"), + ITEM_ARMOR_EQUIP_CHAIN("item.armor.equip_chain"), + ITEM_ARMOR_EQUIP_DIAMOND("item.armor.equip_diamond"), + ITEM_ARMOR_EQUIP_ELYTRA("item.armor.equip_elytra"), + ITEM_ARMOR_EQUIP_GENERIC("item.armor.equip_generic"), + ITEM_ARMOR_EQUIP_GOLD("item.armor.equip_gold"), + ITEM_ARMOR_EQUIP_IRON("item.armor.equip_iron"), + ITEM_ARMOR_EQUIP_LEATHER("item.armor.equip_leather"), + ITEM_BOTTLE_EMPTY("item.bottle.empty"), + ITEM_BOTTLE_FILL("item.bottle.fill"), + ITEM_BOTTLE_FILL_DRAGONBREATH("item.bottle.fill_dragonbreath"), + ITEM_BUCKET_EMPTY("item.bucket.empty"), + ITEM_BUCKET_EMPTY_LAVA("item.bucket.empty_lava"), + ITEM_BUCKET_FILL("item.bucket.fill"), + ITEM_BUCKET_FILL_LAVA("item.bucket.fill_lava"), + ITEM_CHORUS_FRUIT_TELEPORT("item.chorus_fruit.teleport"), + ITEM_ELYTRA_FLYING("item.elytra.flying"), + ITEM_FIRECHARGE_USE("item.firecharge.use"), + ITEM_FLINTANDSTEEL_USE("item.flintandsteel.use"), + ITEM_HOE_TILL("item.hoe.till"), + ITEM_SHIELD_BLOCK("item.shield.block"), + ITEM_SHIELD_BREAK("item.shield.break"), + ITEM_SHOVEL_FLATTEN("item.shovel.flatten"), + ITEM_TOTEM_USE("item.totem.use"), + MUSIC_CREATIVE("music.creative"), + MUSIC_CREDITS("music.credits"), + MUSIC_DRAGON("music.dragon"), + MUSIC_END("music.end"), + MUSIC_GAME("music.game"), + MUSIC_MENU("music.menu"), + MUSIC_NETHER("music.nether"), + RECORD_11("record.11"), + RECORD_13("record.13"), + RECORD_BLOCKS("record.blocks"), + RECORD_CAT("record.cat"), + RECORD_CHIRP("record.chirp"), + RECORD_FAR("record.far"), + RECORD_MALL("record.mall"), + RECORD_MELLOHI("record.mellohi"), + RECORD_STAL("record.stal"), + RECORD_STRAD("record.strad"), + RECORD_WAIT("record.wait"), + RECORD_WARD("record.ward"), + UI_BUTTON_CLICK("ui.button.click"), + UI_TOAST_CHALLENGE_COMPLETE("ui.toast.challenge_complete"), + UI_TOAST_IN("ui.toast.in"), + UI_TOAST_OUT("ui.toast.out"), + WEATHER_RAIN("weather.rain"), + WEATHER_RAIN_ABOVE("weather.rain.above"); + private final String minecraftKey; + + CraftSound(String minecraftKey) { + this.minecraftKey = minecraftKey; + } + + public static String getSound(final Sound sound) { + Validate.notNull(sound, "Sound cannot be null"); + + return CraftSound.valueOf(sound.name()).minecraftKey; + } + + public static SoundEvent getSoundEffect(String s) { + SoundEvent effect = SoundEvent.REGISTRY.getObject(new ResourceLocation(s)); + Preconditions.checkArgument(effect != null, "Sound effect %s does not exist", s); + + return effect; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java b/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java new file mode 100644 index 00000000..f5b00263 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java @@ -0,0 +1,135 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.block.Block; +import net.minecraft.entity.EntityList; +import net.minecraft.item.Item; + +import net.minecraft.stats.StatBase; +import net.minecraft.stats.StatList; +import net.minecraft.util.ResourceLocation; +import org.bukkit.Statistic; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +public class CraftStatistic { + private static final BiMap statistics; + + static { + ImmutableBiMap.Builder statisticBuilder = ImmutableBiMap.builder(); + for (Statistic statistic : Statistic.values()) { + if (statistic == Statistic.PLAY_ONE_TICK) { + statisticBuilder.put("stat.playOneMinute", statistic); + } else { + statisticBuilder.put("stat." + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, statistic.name()), statistic); + } + } + + statistics = statisticBuilder.build(); + } + + private CraftStatistic() {} + + public static Statistic getBukkitStatistic(StatBase statistic) { + return getBukkitStatisticByName(statistic.statId); + } + + public static Statistic getBukkitStatisticByName(String name) { + if (name.startsWith("stat.killEntity.")) { + name = "stat.killEntity"; + } + if (name.startsWith("stat.entityKilledBy.")) { + name = "stat.entityKilledBy"; + } + if (name.startsWith("stat.breakItem.")) { + name = "stat.breakItem"; + } + if (name.startsWith("stat.useItem.")) { + name = "stat.useItem"; + } + if (name.startsWith("stat.mineBlock.")) { + name = "stat.mineBlock"; + } + if (name.startsWith("stat.craftItem.")) { + name = "stat.craftItem"; + } + if (name.startsWith("stat.drop.")) { + name = "stat.drop"; + } + if (name.startsWith("stat.pickup.")) { + name = "stat.pickup"; + } + return statistics.get(name); + } + + public static StatBase getNMSStatistic(Statistic statistic) { + return StatList.getOneShotStat(statistics.inverse().get(statistic)); + } + + public static StatBase getMaterialStatistic(Statistic stat, Material material) { + try { + if (stat == Statistic.MINE_BLOCK) { + return StatList.getBlockStats(CraftMagicNumbers.getBlock(material)); // PAIL: getMineBlockStatistic + } + if (stat == Statistic.CRAFT_ITEM) { + return StatList.getCraftStats(CraftMagicNumbers.getItem(material)); // PAIL: getCraftItemStatistic + } + if (stat == Statistic.USE_ITEM) { + return StatList.getObjectUseStats(CraftMagicNumbers.getItem(material)); // PAIL: getUseItemStatistic + } + if (stat == Statistic.BREAK_ITEM) { + return StatList.getObjectBreakStats(CraftMagicNumbers.getItem(material)); // PAIL: getBreakItemStatistic + } + if (stat == Statistic.PICKUP) { + return StatList.getObjectsPickedUpStats(CraftMagicNumbers.getItem(material)); // PAIL: getPickupStatistic + } + if (stat == Statistic.DROP) { + return StatList.getDroppedObjectStats(CraftMagicNumbers.getItem(material)); // PAIL: getDropItemStatistic + } + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + return null; + } + + public static StatBase getEntityStatistic(Statistic stat, EntityType entity) { + EntityList.EntityEggInfo monsteregginfo = EntityList.ENTITY_EGGS.get(new ResourceLocation(entity.getName())); + + if (monsteregginfo != null) { + if (stat == Statistic.KILL_ENTITY) { + return monsteregginfo.killEntityStat; + } + if (stat == Statistic.ENTITY_KILLED_BY) { + return monsteregginfo.entityKilledByStat; + } + } + return null; + } + + public static EntityType getEntityTypeFromStatistic(StatBase statistic) { + String statisticString = statistic.statId; + return EntityType.fromName(statisticString.substring(statisticString.lastIndexOf(".") + 1)); + } + + public static Material getMaterialFromStatistic(StatBase statistic) { + String statisticString = statistic.statId; + String val = statisticString.substring(statisticString.lastIndexOf(".") + 1); + Item item = Item.REGISTRY.getObject(new ResourceLocation(val)); + if (item != null) { + return Material.getMaterial(Item.getIdFromItem(item)); + } + Block block = Block.REGISTRY.getObject(new ResourceLocation(val)); + if (block != null) { + return Material.getMaterial(Block.getIdFromBlock(block)); + } + try { + return Material.getMaterial(Integer.parseInt(val)); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftTravelAgent.java b/src/main/java/org/bukkit/craftbukkit/CraftTravelAgent.java new file mode 100644 index 00000000..44aae5ca --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftTravelAgent.java @@ -0,0 +1,84 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Teleporter; +import net.minecraft.world.WorldServer; +import org.bukkit.Location; +import org.bukkit.TravelAgent; + +public class CraftTravelAgent extends Teleporter implements TravelAgent { + + public static TravelAgent DEFAULT = null; + + private int searchRadius = 128; + private int creationRadius = 16; + private boolean canCreatePortal = true; + + public CraftTravelAgent(WorldServer worldserver) { + super(worldserver); + if (DEFAULT == null && worldserver.dimension == 0) { + DEFAULT = this; + } + } + + @Override + public Location findOrCreate(Location target) { + WorldServer worldServer = ((CraftWorld) target.getWorld()).getHandle(); + + Location found = this.findPortal(target); + if (found == null) { + if (this.getCanCreatePortal() && this.createPortal(target)) { + found = this.findPortal(target); + } else { + found = target; // fallback to original if unable to find or create + } + } + + return found; + } + + @Override + public Location findPortal(Location location) { + Teleporter pta = ((CraftWorld) location.getWorld()).getHandle().getDefaultTeleporter(); + BlockPos found = pta.findPortal(location.getX(), location.getY(), location.getZ(), this.getSearchRadius()); + return found != null ? new Location(location.getWorld(), found.getX(), found.getY(), found.getZ(), location.getYaw(), location.getPitch()) : null; + } + + @Override + public boolean createPortal(Location location) { + Teleporter pta = ((CraftWorld) location.getWorld()).getHandle().getDefaultTeleporter(); + return pta.createPortal(location.getX(), location.getY(), location.getZ(), this.getCreationRadius()); + } + + @Override + public TravelAgent setSearchRadius(int radius) { + this.searchRadius = radius; + return this; + } + + @Override + public int getSearchRadius() { + return this.searchRadius; + } + + @Override + public TravelAgent setCreationRadius(int radius) { + this.creationRadius = radius < 2 ? 0 : radius; + return this; + } + + @Override + public int getCreationRadius() { + return this.creationRadius; + } + + @Override + public boolean getCanCreatePortal() { + return this.canCreatePortal; + } + + @Override + public void setCanCreatePortal(boolean create) { + this.canCreatePortal = create; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java new file mode 100644 index 00000000..809510fa --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -0,0 +1,1820 @@ +package org.bukkit.craftbukkit; + +import com.google.common.base.Preconditions; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +import net.minecraft.block.BlockChorusFlower; +import net.minecraft.block.BlockLeaves; +import net.minecraft.block.BlockOldLeaf; +import net.minecraft.block.BlockOldLog; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.BlockRedstoneDiode; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityAreaEffectCloud; +import net.minecraft.entity.EntityHanging; +import net.minecraft.entity.EntityLeashKnot; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.boss.EntityDragon; +import net.minecraft.entity.boss.EntityWither; +import net.minecraft.entity.effect.EntityLightningBolt; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.item.EntityBoat; +import net.minecraft.entity.item.EntityEnderCrystal; +import net.minecraft.entity.item.EntityEnderEye; +import net.minecraft.entity.item.EntityEnderPearl; +import net.minecraft.entity.item.EntityExpBottle; +import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.entity.item.EntityFireworkRocket; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.entity.item.EntityMinecartChest; +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import net.minecraft.entity.item.EntityMinecartEmpty; +import net.minecraft.entity.item.EntityMinecartFurnace; +import net.minecraft.entity.item.EntityMinecartHopper; +import net.minecraft.entity.item.EntityMinecartMobSpawner; +import net.minecraft.entity.item.EntityMinecartTNT; +import net.minecraft.entity.item.EntityPainting; +import net.minecraft.entity.item.EntityTNTPrimed; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.monster.EntityBlaze; +import net.minecraft.entity.monster.EntityCaveSpider; +import net.minecraft.entity.monster.EntityCreeper; +import net.minecraft.entity.monster.EntityElderGuardian; +import net.minecraft.entity.monster.EntityEnderman; +import net.minecraft.entity.monster.EntityEndermite; +import net.minecraft.entity.monster.EntityEvoker; +import net.minecraft.entity.monster.EntityGhast; +import net.minecraft.entity.monster.EntityGiantZombie; +import net.minecraft.entity.monster.EntityGuardian; +import net.minecraft.entity.monster.EntityHusk; +import net.minecraft.entity.monster.EntityIllusionIllager; +import net.minecraft.entity.monster.EntityIronGolem; +import net.minecraft.entity.monster.EntityMagmaCube; +import net.minecraft.entity.monster.EntityPigZombie; +import net.minecraft.entity.monster.EntityPolarBear; +import net.minecraft.entity.monster.EntityShulker; +import net.minecraft.entity.monster.EntitySilverfish; +import net.minecraft.entity.monster.EntitySkeleton; +import net.minecraft.entity.monster.EntitySlime; +import net.minecraft.entity.monster.EntitySnowman; +import net.minecraft.entity.monster.EntitySpider; +import net.minecraft.entity.monster.EntityStray; +import net.minecraft.entity.monster.EntityVex; +import net.minecraft.entity.monster.EntityVindicator; +import net.minecraft.entity.monster.EntityWitch; +import net.minecraft.entity.monster.EntityWitherSkeleton; +import net.minecraft.entity.monster.EntityZombie; +import net.minecraft.entity.monster.EntityZombieVillager; +import net.minecraft.entity.passive.EntityBat; +import net.minecraft.entity.passive.EntityChicken; +import net.minecraft.entity.passive.EntityCow; +import net.minecraft.entity.passive.EntityDonkey; +import net.minecraft.entity.passive.EntityHorse; +import net.minecraft.entity.passive.EntityLlama; +import net.minecraft.entity.passive.EntityMooshroom; +import net.minecraft.entity.passive.EntityMule; +import net.minecraft.entity.passive.EntityOcelot; +import net.minecraft.entity.passive.EntityParrot; +import net.minecraft.entity.passive.EntityPig; +import net.minecraft.entity.passive.EntityRabbit; +import net.minecraft.entity.passive.EntitySheep; +import net.minecraft.entity.passive.EntitySkeletonHorse; +import net.minecraft.entity.passive.EntitySquid; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.entity.passive.EntityWolf; +import net.minecraft.entity.passive.EntityZombieHorse; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityDragonFireball; +import net.minecraft.entity.projectile.EntityEgg; +import net.minecraft.entity.projectile.EntityEvokerFangs; +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.entity.projectile.EntityLargeFireball; +import net.minecraft.entity.projectile.EntityLlamaSpit; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.entity.projectile.EntityShulkerBullet; +import net.minecraft.entity.projectile.EntitySmallFireball; +import net.minecraft.entity.projectile.EntitySnowball; +import net.minecraft.entity.projectile.EntitySpectralArrow; +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.entity.projectile.EntityWitherSkull; +import net.minecraft.init.Blocks; + +import net.minecraft.network.Packet; +import net.minecraft.network.play.server.*; +import net.minecraft.server.management.PlayerChunkMapEntry; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.MinecraftException; +import net.minecraft.world.WorldProviderEnd; +import net.minecraft.world.WorldProviderHell; +import net.minecraft.world.WorldProviderSurface; +import net.minecraft.world.WorldServer; +import net.minecraft.world.gen.ChunkProviderServer; +import net.minecraft.world.gen.feature.WorldGenBigMushroom; +import net.minecraft.world.gen.feature.WorldGenBigTree; +import net.minecraft.world.gen.feature.WorldGenBirchTree; +import net.minecraft.world.gen.feature.WorldGenCanopyTree; +import net.minecraft.world.gen.feature.WorldGenMegaJungle; +import net.minecraft.world.gen.feature.WorldGenMegaPineTree; +import net.minecraft.world.gen.feature.WorldGenSavannaTree; +import net.minecraft.world.gen.feature.WorldGenShrub; +import net.minecraft.world.gen.feature.WorldGenSwamp; +import net.minecraft.world.gen.feature.WorldGenTaiga1; +import net.minecraft.world.gen.feature.WorldGenTaiga2; +import net.minecraft.world.gen.feature.WorldGenTrees; +import net.minecraftforge.common.util.BlockSnapshot; +import net.minecraftforge.fml.common.registry.EntityRegistry; +import org.apache.commons.lang3.Validate; +import org.bukkit.BlockChangeDelegate; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Difficulty; +import org.bukkit.Effect; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.TreeType; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.entity.*; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.metadata.BlockMetadataStore; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.*; +import org.bukkit.entity.Entity; +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.PoweredMinecart; +import org.bukkit.entity.minecart.SpawnerMinecart; +import org.bukkit.entity.minecart.StorageMinecart; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.world.SpawnChangeEvent; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.StandardMessenger; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionType; +import org.bukkit.util.Consumer; +import org.bukkit.util.Vector; + +public class CraftWorld implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; + + private final WorldServer world; + private WorldBorder worldBorder; + private Environment environment; + private final CraftServer server = (CraftServer) Bukkit.getServer(); + public ChunkGenerator generator; + private final List populators = new ArrayList(); + private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this); + private int monsterSpawn = -1; + private int animalSpawn = -1; + private int waterAnimalSpawn = -1; + private int ambientSpawn = -1; + private int chunkLoadCount = 0; + private int chunkGCTickCount; + + private static final Random rand = new Random(); + + public CraftWorld(WorldServer world, ChunkGenerator gen, Environment env) { + this.world = world; + this.generator = gen; + + environment = env; + + if (server.chunkGCPeriod > 0) { + chunkGCTickCount = rand.nextInt(server.chunkGCPeriod); + } + } + + public Block getBlockAt(int x, int y, int z) { + Chunk chunk = getChunkAt(x >> 4, z >> 4); + return chunk == null ? null : chunk.getBlock(x & 0xF, y & 0xFF, z & 0xF); + } + + public int getBlockTypeIdAt(int x, int y, int z) { + return CraftMagicNumbers.getId(world.getBlockState(new BlockPos(x, y, z)).getBlock()); + } + + public int getHighestBlockYAt(int x, int z) { + if (!isChunkLoaded(x >> 4, z >> 4)) { + loadChunk(x >> 4, z >> 4); + } + + return world.getHeight(new BlockPos(x, 0, z)).getY(); + } + + public Location getSpawnLocation() { + BlockPos spawn = world.getSpawnPoint(); + return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ()); + } + + @Override + public boolean setSpawnLocation(Location location) { + Preconditions.checkArgument(location != null, "location"); + + return equals(location.getWorld()) && setSpawnLocation(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + public boolean setSpawnLocation(int x, int y, int z) { + try { + Location previousLocation = getSpawnLocation(); + world.worldInfo.setSpawn(new BlockPos(x, y, z)); + + // Notify anyone who's listening. + SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); + server.getPluginManager().callEvent(event); + + return true; + } catch (Exception e) { + return false; + } + } + + public Chunk getChunkAt(int x, int z) { + net.minecraft.world.chunk.Chunk chunk = this.world.getChunkProvider().provideChunk(x, z); + return chunk == null ? null : chunk.bukkitChunk; + } + + public Chunk getChunkAt(Block block) { + return getChunkAt(block.getX() >> 4, block.getZ() >> 4); + } + + public boolean isChunkLoaded(int x, int z) { + return world.getChunkProvider().chunkExists(x, z); + } + + public Chunk[] getLoadedChunks() { + Object[] chunks = world.getChunkProvider().id2ChunkMap.values().toArray(); + Chunk[] craftChunks = new CraftChunk[chunks.length]; + + for (int i = 0; i < chunks.length; i++) { + net.minecraft.world.chunk.Chunk chunk = (net.minecraft.world.chunk.Chunk) chunks[i]; + craftChunks[i] = chunk.bukkitChunk; + } + + return craftChunks; + } + + public void loadChunk(int x, int z) { + loadChunk(x, z, true); + } + + public boolean unloadChunk(Chunk chunk) { + return unloadChunk(chunk.getX(), chunk.getZ()); + } + + public boolean unloadChunk(int x, int z) { + return unloadChunk(x, z, true); + } + + public boolean unloadChunk(int x, int z, boolean save) { + return unloadChunk(x, z, save, false); + } + + public boolean unloadChunkRequest(int x, int z) { + return unloadChunkRequest(x, z, true); + } + + public boolean unloadChunkRequest(int x, int z, boolean safe) { + if (safe && isChunkInUse(x, z)) { + return false; + } + + net.minecraft.world.chunk.Chunk chunk = world.getChunkProvider().getLoadedChunk(x, z); + if (chunk != null) { + world.getChunkProvider().queueUnload(chunk); + } + + return true; + } + + public boolean unloadChunk(int x, int z, boolean save, boolean safe) { + if (isChunkInUse(x, z)) { + return false; + } + + return unloadChunk0(x, z, save); + } + + private boolean unloadChunk0(int x, int z, boolean save) { + net.minecraft.world.chunk.Chunk chunk = world.getChunkProvider().getChunkIfLoaded(x, z); + if (chunk == null) { + return true; + } + + // If chunk had previously been queued to save, must do save to avoid loss of that data + return world.getChunkProvider().unloadChunk(chunk, chunk.mustSave || save); + } + + public boolean regenerateChunk(int x, int z) { + if (!unloadChunk0(x, z, false)) { + return false; + } + + final long chunkKey = ChunkPos.asLong(x, z); + world.getChunkProvider().droppedChunksSet.remove(chunkKey); + + net.minecraft.world.chunk.Chunk chunk = null; + + chunk = world.getChunkProvider().chunkGenerator.generateChunk(x, z); + PlayerChunkMapEntry playerChunk = world.getPlayerChunkMap().getEntry(x, z); + if (playerChunk != null) { + playerChunk.chunk = chunk; + } + + if (chunk != null) { + world.getChunkProvider().id2ChunkMap.put(chunkKey, chunk); + + chunk.onLoad(); + chunk.populateCB(world.getChunkProvider(), world.getChunkProvider().chunkGenerator, true); + + refreshChunk(x, z); + } + + return chunk != null; + } + + public boolean refreshChunk(int x, int z) { + if (!isChunkLoaded(x, z)) { + return false; + } + + int px = x << 4; + int pz = z << 4; + + // If there are more than 64 updates to a chunk at once, it will update all 'touched' sections within the chunk + // And will include biome data if all sections have been 'touched' + // This flags 65 blocks distributed across all the sections of the chunk, so that everything is sent, including biomes + int height = getMaxHeight() / 16; + for (int idx = 0; idx < 64; idx++) { + world.notifyBlockUpdate(new BlockPos(px + (idx / height), ((idx % height) * 16), pz), Blocks.AIR.getDefaultState(), Blocks.STONE.getDefaultState(), 3); + } + world.notifyBlockUpdate(new BlockPos(px + 15, (height * 16) - 1, pz + 15), Blocks.AIR.getDefaultState(), Blocks.STONE.getDefaultState(), 3); + + return true; + } + + public boolean isChunkInUse(int x, int z) { + return world.getPlayerChunkMap().isChunkInUse(x, z); + } + + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp( "chunk load"); // Spigot + chunkLoadCount++; + if (generate) { + // Use the default variant of loadChunk when generate == true. + return world.getChunkProvider().provideChunk(x, z) != null; + } + + return world.getChunkProvider().loadChunk(x, z) != null; + } + + public boolean isChunkLoaded(Chunk chunk) { + return isChunkLoaded(chunk.getX(), chunk.getZ()); + } + + public void loadChunk(Chunk chunk) { + loadChunk(chunk.getX(), chunk.getZ()); + ((CraftChunk) getChunkAt(chunk.getX(), chunk.getZ())).getHandle().bukkitChunk = chunk; + } + + public WorldServer getHandle() { + return world; + } + + public Item dropItem(Location loc, ItemStack item) { + Validate.notNull(item, "Cannot drop a Null item."); + Validate.isTrue(item.getTypeId() != 0, "Cannot drop AIR."); + EntityItem entity = new EntityItem(world, loc.getX(), loc.getY(), loc.getZ(), CraftItemStack.asNMSCopy(item)); + entity.pickupDelay = 10; + world.spawnEntity(entity, SpawnReason.CUSTOM); + // TODO this is inconsistent with how Entity.getBukkitEntity() works. + // However, this entity is not at the moment backed by a server entity class so it may be left. + return new CraftItem(world.getServer(), entity); + } + + private static void randomLocationWithinBlock(Location loc, double xs, double ys, double zs) { + double prevX = loc.getX(); + double prevY = loc.getY(); + double prevZ = loc.getZ(); + loc.add(xs, ys, zs); + if (loc.getX() < Math.floor(prevX)) { + loc.setX(Math.floor(prevX)); + } + if (loc.getX() >= Math.ceil(prevX)) { + loc.setX(Math.ceil(prevX - 0.01)); + } + if (loc.getY() < Math.floor(prevY)) { + loc.setY(Math.floor(prevY)); + } + if (loc.getY() >= Math.ceil(prevY)) { + loc.setY(Math.ceil(prevY - 0.01)); + } + if (loc.getZ() < Math.floor(prevZ)) { + loc.setZ(Math.floor(prevZ)); + } + if (loc.getZ() >= Math.ceil(prevZ)) { + loc.setZ(Math.ceil(prevZ - 0.01)); + } + } + + public Item dropItemNaturally(Location loc, ItemStack item) { + double xs = world.rand.nextFloat() * 0.7F - 0.35D; + double ys = world.rand.nextFloat() * 0.7F - 0.35D; + double zs = world.rand.nextFloat() * 0.7F - 0.35D; + loc = loc.clone(); + // Makes sure the new item is created within the block the location points to. + // This prevents item spill in 1-block wide farms. + randomLocationWithinBlock(loc, xs, ys, zs); + return dropItem(loc, item); + } + + public Arrow spawnArrow(Location loc, Vector velocity, float speed, float spread) { + return spawnArrow(loc, velocity, speed, spread, Arrow.class); + } + + public T spawnArrow(Location loc, Vector velocity, float speed, float spread, Class clazz) { + Validate.notNull(loc, "Can not spawn arrow with a null location"); + Validate.notNull(velocity, "Can not spawn arrow with a null velocity"); + Validate.notNull(clazz, "Can not spawn an arrow with no class"); + + EntityArrow arrow; + if (TippedArrow.class.isAssignableFrom(clazz)) { + arrow = new EntityTippedArrow(world); + ((EntityTippedArrow) arrow).setType(CraftPotionUtil.fromBukkit(new PotionData(PotionType.WATER, false, false))); + } else if (SpectralArrow.class.isAssignableFrom(clazz)) { + arrow = new EntitySpectralArrow(world); + } else { + arrow = new EntityTippedArrow(world); + } + + arrow.setLocationAndAngles(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); + arrow.shoot(velocity.getX(), velocity.getY(), velocity.getZ(), speed, spread); + world.spawnEntity(arrow); + return (T) arrow.getBukkitEntity(); + } + + public Entity spawnEntity(Location loc, EntityType entityType) { + if (EntityRegistry.entityClassMap.get(entityType.getName()) != null) { + net.minecraft.entity.Entity entity = null; + entity = getEntity(EntityRegistry.entityClassMap.get(entityType.getName()), world); + if (entity != null) { + entity.setLocationAndAngles(loc.getX(), loc.getY(), loc.getZ(), 0, 0); + world.spawnEntity(entity, SpawnReason.CUSTOM); + return entity.getBukkitEntity(); + } + } + return spawn(loc, entityType.getEntityClass()); + } + + private net.minecraft.entity.Entity getEntity(Class aClass, WorldServer world) { + EntityLiving entity = null; + try { + entity = (net.minecraft.entity.EntityLiving) aClass.getConstructor(new Class[] { net.minecraft.world.World.class }).newInstance(new Object[] { world }); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + return entity; + } + + public LightningStrike strikeLightning(Location loc) { + EntityLightningBolt lightning = new EntityLightningBolt(world, loc.getX(), loc.getY(), loc.getZ(), false); + world.addWeatherEffect(lightning); + return new CraftLightningStrike(server, lightning); + } + + public LightningStrike strikeLightningEffect(Location loc) { + EntityLightningBolt lightning = new EntityLightningBolt(world, loc.getX(), loc.getY(), loc.getZ(), true); + world.addWeatherEffect(lightning); + return new CraftLightningStrike(server, lightning); + } + + public boolean generateTree(Location loc, TreeType type) { + BlockPos pos = new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + + net.minecraft.world.gen.feature.WorldGenerator gen; + switch (type) { + case BIG_TREE: + gen = new WorldGenBigTree(true); + break; + case BIRCH: + gen = new WorldGenBirchTree(true, false); + break; + case REDWOOD: + gen = new WorldGenTaiga2(true); + break; + case TALL_REDWOOD: + gen = new WorldGenTaiga1(); + break; + case JUNGLE: + IBlockState iblockdata1 = Blocks.LOG.getDefaultState().withProperty(BlockOldLog.VARIANT, BlockPlanks.EnumType.JUNGLE); + IBlockState iblockdata2 = Blocks.LEAVES.getDefaultState().withProperty(BlockOldLeaf.VARIANT, BlockPlanks.EnumType.JUNGLE).withProperty(BlockLeaves.CHECK_DECAY, Boolean.valueOf(false)); + gen = new WorldGenMegaJungle(true, 10, 20, iblockdata1, iblockdata2); // Magic values as in BlockSapling + break; + case SMALL_JUNGLE: + iblockdata1 = Blocks.LOG.getDefaultState().withProperty(BlockOldLog.VARIANT, BlockPlanks.EnumType.JUNGLE); + iblockdata2 = Blocks.LEAVES.getDefaultState().withProperty(BlockOldLeaf.VARIANT, BlockPlanks.EnumType.JUNGLE).withProperty(BlockLeaves.CHECK_DECAY, Boolean.valueOf(false)); + gen = new WorldGenTrees(true, 4 + rand.nextInt(7), iblockdata1, iblockdata2, false); + break; + case COCOA_TREE: + iblockdata1 = Blocks.LOG.getDefaultState().withProperty(BlockOldLog.VARIANT, BlockPlanks.EnumType.JUNGLE); + iblockdata2 = Blocks.LEAVES.getDefaultState().withProperty(BlockOldLeaf.VARIANT, BlockPlanks.EnumType.JUNGLE).withProperty(BlockLeaves.CHECK_DECAY, Boolean.valueOf(false)); + gen = new WorldGenTrees(true, 4 + rand.nextInt(7), iblockdata1, iblockdata2, true); + break; + case JUNGLE_BUSH: + iblockdata1 = Blocks.LOG.getDefaultState().withProperty(BlockOldLog.VARIANT, BlockPlanks.EnumType.JUNGLE); + iblockdata2 = Blocks.LEAVES.getDefaultState().withProperty(BlockOldLeaf.VARIANT, BlockPlanks.EnumType.OAK).withProperty(BlockLeaves.CHECK_DECAY, Boolean.valueOf(false)); + gen = new WorldGenShrub(iblockdata1, iblockdata2); + break; + case RED_MUSHROOM: + gen = new WorldGenBigMushroom(Blocks.RED_MUSHROOM_BLOCK); + break; + case BROWN_MUSHROOM: + gen = new WorldGenBigMushroom(Blocks.BROWN_MUSHROOM_BLOCK); + break; + case SWAMP: + gen = new WorldGenSwamp(); + break; + case ACACIA: + gen = new WorldGenSavannaTree(true); + break; + case DARK_OAK: + gen = new WorldGenCanopyTree(true); + break; + case MEGA_REDWOOD: + gen = new WorldGenMegaPineTree(false, rand.nextBoolean()); + break; + case TALL_BIRCH: + gen = new WorldGenBirchTree(true, true); + break; + case CHORUS_PLANT: + BlockChorusFlower.generatePlant(world, pos, rand, 8); + return true; + case TREE: + default: + gen = new WorldGenTrees(true); + break; + } + + return gen.generate(world, rand, pos); + } + + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { + world.captureTreeGeneration = true; + world.captureBlockSnapshots = true; + boolean grownTree = generateTree(loc, type); + world.captureBlockSnapshots = false; + world.captureTreeGeneration = false; + if (grownTree) { // Copy block data to delegate + for (BlockSnapshot blocksnapshot : this.world.capturedBlockSnapshots) { + BlockPos position = blocksnapshot.getPos(); + int x = position.getX(); + int y = position.getY(); + int z = position.getZ(); + net.minecraft.block.state.IBlockState oldBlock = world.getBlockState(position); + int typeId = net.minecraft.block.Block.getIdFromBlock(blocksnapshot.getReplacedBlock().getBlock()); + int data = blocksnapshot.getMeta(); + int flag = blocksnapshot.getFlag(); + delegate.setTypeIdAndData(x, y, z, typeId, data); + IBlockState newBlock = world.getBlockState(position); + world.markAndNotifyBlock(position, null, oldBlock, newBlock, flag); + } + world.capturedBlockSnapshots.clear(); + return true; + } else { + world.capturedBlockSnapshots.clear(); + return false; + } + } + + public TileEntity getTileEntityAt(final int x, final int y, final int z) { + return world.getTileEntity(new BlockPos(x, y, z)); + } + + public String getName() { + return world.worldInfo.getWorldName(); + } + + @Deprecated + public long getId() { + return world.worldInfo.getSeed(); + } + + public UUID getUID() { + return world.getSaveHandler().getUUID(); + } + + @Override + public String toString() { + return "CraftWorld{name=" + getName() + '}'; + } + + public long getTime() { + long time = getFullTime() % 24000; + if (time < 0) { + time += 24000; + } + return time; + } + + public void setTime(long time) { + long margin = (time - getFullTime()) % 24000; + if (margin < 0) { + margin += 24000; + } + setFullTime(getFullTime() + margin); + } + + public long getFullTime() { + return world.getWorldTime(); + } + + public void setFullTime(long time) { + world.setWorldTime(time); + + // Forces the client to update to the new time immediately + for (Player p : getPlayers()) { + CraftPlayer cp = (CraftPlayer) p; + if (cp.getHandle().connection == null) { + continue; + } + + cp.getHandle().connection.sendPacket(new SPacketTimeUpdate(cp.getHandle().world.getTotalWorldTime(), cp.getHandle().getPlayerTime(), cp.getHandle().world.getGameRules().getBoolean("doDaylightCycle"))); + } + } + + public boolean createExplosion(double x, double y, double z, float power) { + return createExplosion(x, y, z, power, false, true); + } + + public boolean createExplosion(double x, double y, double z, float power, boolean setFire) { + return createExplosion(x, y, z, power, setFire, true); + } + + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks) { + return !world.newExplosion(null, x, y, z, power, setFire, breakBlocks).wasCanceled; + } + + // Paper start + @Override + public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { + return !world.newExplosion(source != null ? ((CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks).wasCanceled; + } + // Paper end + + @Override + public boolean createExplosion(Location loc, float power) { + return createExplosion(loc, power, false); + } + + public boolean createExplosion(Location loc, float power, boolean setFire) { + return createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire); + } + + public Environment getEnvironment() { + return environment; + } + + public void setEnvironment(Environment env) { + if (environment != env) { + environment = env; + world.provider = net.minecraft.world.WorldProvider.getProviderForDimension(environment.getId()); + } + } + + public Block getBlockAt(Location location) { + return getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + public int getBlockTypeIdAt(Location location) { + return getBlockTypeIdAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + public int getHighestBlockYAt(Location location) { + return getHighestBlockYAt(location.getBlockX(), location.getBlockZ()); + } + + public Chunk getChunkAt(Location location) { + return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4); + } + + public ChunkGenerator getGenerator() { + return generator; + } + + public List getPopulators() { + return populators; + } + + public Block getHighestBlockAt(int x, int z) { + return getBlockAt(x, getHighestBlockYAt(x, z), z); + } + + public Block getHighestBlockAt(Location location) { + return getHighestBlockAt(location.getBlockX(), location.getBlockZ()); + } + + public Biome getBiome(int x, int z) { + return CraftBlock.biomeBaseToBiome(this.world.getBiome(new BlockPos(x, 0, z))); + } + + public void setBiome(int x, int z, Biome bio) { + net.minecraft.world.biome.Biome bb = CraftBlock.biomeToBiomeBase(bio); + if (this.world.isBlockLoaded(new BlockPos(x, 0, z))) { + net.minecraft.world.chunk.Chunk chunk = this.world.getChunkFromBlockCoords(new BlockPos(x, 0, z)); + + if (chunk != null) { + byte[] biomevals = chunk.getBiomeArray(); + biomevals[((z & 0xF) << 4) | (x & 0xF)] = (byte) net.minecraft.world.biome.Biome.REGISTRY.getIDForObject(bb); + + chunk.markDirty(); // SPIGOT-2890 + } + } + } + + public double getTemperature(int x, int z) { + return this.world.getBiome(new BlockPos(x, 0, z)).getDefaultTemperature(); + } + + public double getHumidity(int x, int z) { + return this.world.getBiome(new BlockPos(x, 0, z)).getRainfall(); + } + + public List getEntities() { + List list = new ArrayList(); + + for (Object o : world.loadedEntityList) { + if (o instanceof net.minecraft.entity.Entity) { + net.minecraft.entity.Entity mcEnt = (net.minecraft.entity.Entity) o; + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null + if (bukkitEntity != null) { + list.add(bukkitEntity); + } + } + } + + return list; + } + + public List getLivingEntities() { + List list = new ArrayList(); + + for (Object o : world.loadedEntityList) { + if (o instanceof net.minecraft.entity.Entity) { + net.minecraft.entity.Entity mcEnt = (net.minecraft.entity.Entity) o; + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null + if (bukkitEntity != null && bukkitEntity instanceof LivingEntity) { + list.add((LivingEntity) bukkitEntity); + } + } + } + + return list; + } + + @SuppressWarnings("unchecked") + @Deprecated + public Collection getEntitiesByClass(Class... classes) { + return (Collection)getEntitiesByClasses(classes); + } + + @SuppressWarnings("unchecked") + public Collection getEntitiesByClass(Class clazz) { + Collection list = new ArrayList(); + + for (Object entity: world.loadedEntityList) { + if (entity instanceof net.minecraft.entity.Entity) { + Entity bukkitEntity = ((net.minecraft.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { + continue; + } + + Class bukkitClass = bukkitEntity.getClass(); + + if (clazz.isAssignableFrom(bukkitClass)) { + list.add((T) bukkitEntity); + } + } + } + + return list; + } + + public Collection getEntitiesByClasses(Class... classes) { + Collection list = new ArrayList(); + + for (Object entity: world.loadedEntityList) { + if (entity instanceof net.minecraft.entity.Entity) { + Entity bukkitEntity = ((net.minecraft.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { + continue; + } + + Class bukkitClass = bukkitEntity.getClass(); + + for (Class clazz : classes) { + if (clazz.isAssignableFrom(bukkitClass)) { + list.add(bukkitEntity); + break; + } + } + } + } + + return list; + } + + @Override + public Collection getNearbyEntities(Location location, double x, double y, double z) { + if (location == null || !location.getWorld().equals(this)) { + return Collections.emptyList(); + } + + AxisAlignedBB bb = new AxisAlignedBB(location.getX() - x, location.getY() - y, location.getZ() - z, location.getX() + x, location.getY() + y, location.getZ() + z); + List entityList = getHandle().getEntitiesInAABBexcluding(null, bb, null); + List bukkitEntityList = new ArrayList(entityList.size()); + for (Object entity : entityList) { + bukkitEntityList.add(((net.minecraft.entity.Entity) entity).getBukkitEntity()); + } + return bukkitEntityList; + } + + public List getPlayers() { + List list = new ArrayList(world.playerEntities.size()); + + for (EntityPlayer human : world.playerEntities) { + HumanEntity bukkitEntity = human.getBukkitEntity(); + + if ((bukkitEntity != null) && (bukkitEntity instanceof Player)) { + list.add((Player) bukkitEntity); + } + } + + return list; + } + + // Paper start - getEntity by UUID API + public Entity getEntity(UUID uuid) { + Validate.notNull(uuid, "UUID cannot be null"); + net.minecraft.entity.Entity entity = world.getEntityFromUuid(uuid); + return entity == null ? null : entity.getBukkitEntity(); + } + // Paper end + + @Override + public void save() { + // Spigot start + save(true); + } + + public void save(boolean forceSave) { + this.server.checkSaveState(); + try { + boolean oldSave = world.disableLevelSaving; + + world.disableLevelSaving = false; + world.saveAllChunks(forceSave, null); + + world.disableLevelSaving = oldSave; + } catch (MinecraftException ex) { + ex.printStackTrace(); + } + } + + public boolean isAutoSave() { + return !world.disableLevelSaving; + } + + public void setAutoSave(boolean value) { + world.disableLevelSaving = !value; + } + + public void setDifficulty(Difficulty difficulty) { + this.getHandle().worldInfo.setDifficulty(EnumDifficulty.getDifficultyEnum(difficulty.getValue())); + } + + public Difficulty getDifficulty() { + return Difficulty.getByValue(this.getHandle().getDifficulty().ordinal()); + } + + public BlockMetadataStore getBlockMetadata() { + return blockMetadata; + } + + public boolean hasStorm() { + return world.worldInfo.isRaining(); + } + + public void setStorm(boolean hasStorm) { + world.worldInfo.setRaining(hasStorm); + setWeatherDuration(0); // Reset weather duration (legacy behaviour) + } + + public int getWeatherDuration() { + return world.worldInfo.getRainTime(); + } + + public void setWeatherDuration(int duration) { + world.worldInfo.setRainTime(duration); + } + + public boolean isThundering() { + return world.worldInfo.isThundering(); + } + + public void setThundering(boolean thundering) { + world.worldInfo.setThundering(thundering); + setThunderDuration(0); // Reset weather duration (legacy behaviour) + } + + public int getThunderDuration() { + return world.worldInfo.getThunderTime(); + } + + public void setThunderDuration(int duration) { + world.worldInfo.setThunderTime(duration); + } + + public long getSeed() { + return world.worldInfo.getSeed(); + } + + public boolean getPVP() { + return world.pvpMode; + } + + public void setPVP(boolean pvp) { + world.pvpMode = pvp; + } + + public void playEffect(Player player, Effect effect, int data) { + playEffect(player.getLocation(), effect, data, 0); + } + + public void playEffect(Location location, Effect effect, int data) { + playEffect(location, effect, data, 64); + } + + public void playEffect(Location loc, Effect effect, T data) { + playEffect(loc, effect, data, 64); + } + + public void playEffect(Location loc, Effect effect, T data, int radius) { + if (data != null) { + Validate.isTrue(effect.getData() != null && effect.getData().isAssignableFrom(data.getClass()), "Wrong kind of data for this effect!"); + } else { + Validate.isTrue(effect.getData() == null, "Wrong kind of data for this effect!"); + } + + if (data != null && data.getClass().equals( MaterialData.class )) { + MaterialData materialData = (MaterialData) data; + Validate.isTrue( materialData.getItemType().isBlock(), "Material must be block" ); + spigot().playEffect( loc, effect, materialData.getItemType().getId(), materialData.getData(), 0, 0, 0, 1, 1, radius ); + } else { + int dataValue = data == null ? 0 : CraftEffect.getDataValue( effect, data ); + playEffect( loc, effect, dataValue, radius ); + } + } + + public void playEffect(Location location, Effect effect, int data, int radius) { + spigot().playEffect( location, effect, data, 0, 0, 0, 0, 1, 1, radius ); + } + + public T spawn(Location location, Class clazz) throws IllegalArgumentException { + return spawn(location, clazz, null, SpawnReason.CUSTOM); + } + + @Override + public T spawn(Location location, Class clazz, Consumer function) throws IllegalArgumentException { + return spawn(location, clazz, function, SpawnReason.CUSTOM); + } + + @Override + public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException { + Validate.notNull(data, "MaterialData cannot be null"); + return spawnFallingBlock(location, data.getItemType(), data.getData()); + } + + public FallingBlock spawnFallingBlock(Location location, org.bukkit.Material material, byte data) throws IllegalArgumentException { + Validate.notNull(location, "Location cannot be null"); + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(material.isBlock(), "Material must be a block"); + + EntityFallingBlock entity = new EntityFallingBlock(world, location.getX(), location.getY(), location.getZ(), CraftMagicNumbers.getBlock(material).getStateFromMeta(data)); + entity.ticksExisted = 1; + + world.spawnEntity(entity, SpawnReason.CUSTOM); + return (FallingBlock) entity.getBukkitEntity(); + } + + public FallingBlock spawnFallingBlock(Location location, int blockId, byte blockData) throws IllegalArgumentException { + return spawnFallingBlock(location, org.bukkit.Material.getBlockMaterial(blockId), blockData); + } + + @SuppressWarnings("unchecked") + public net.minecraft.entity.Entity createEntity(Location location, Class clazz) throws IllegalArgumentException { + if (location == null || clazz == null) { + throw new IllegalArgumentException("Location or entity class cannot be null"); + } + + net.minecraft.entity.Entity entity = null; + + double x = location.getX(); + double y = location.getY(); + double z = location.getZ(); + float pitch = location.getPitch(); + float yaw = location.getYaw(); + + // order is important for some of these + if (Boat.class.isAssignableFrom(clazz)) { + entity = new EntityBoat(world, x, y, z); + entity.setLocationAndAngles(x, y, z, yaw, pitch); + } else if (FallingBlock.class.isAssignableFrom(clazz)) { + entity = new EntityFallingBlock(world, x, y, z, world.getBlockState(new BlockPos(x, y, z))); + } else if (Projectile.class.isAssignableFrom(clazz)) { + if (Snowball.class.isAssignableFrom(clazz)) { + entity = new EntitySnowball(world, x, y, z); + } else if (Egg.class.isAssignableFrom(clazz)) { + entity = new EntityEgg(world, x, y, z); + } else if (Arrow.class.isAssignableFrom(clazz)) { + if (TippedArrow.class.isAssignableFrom(clazz)) { + entity = new EntityTippedArrow(world); + ((EntityTippedArrow) entity).setType(CraftPotionUtil.fromBukkit(new PotionData(PotionType.WATER, false, false))); + } else if (SpectralArrow.class.isAssignableFrom(clazz)) { + entity = new EntitySpectralArrow(world); + } else { + entity = new EntityTippedArrow(world); + } + entity.setLocationAndAngles(x, y, z, 0, 0); + } else if (ThrownExpBottle.class.isAssignableFrom(clazz)) { + entity = new EntityExpBottle(world); + entity.setLocationAndAngles(x, y, z, 0, 0); + } else if (EnderPearl.class.isAssignableFrom(clazz)) { + entity = new EntityEnderPearl(world); + entity.setLocationAndAngles(x, y, z, 0, 0); + } else if (ThrownPotion.class.isAssignableFrom(clazz)) { + if (LingeringPotion.class.isAssignableFrom(clazz)) { + entity = new EntityPotion(world, x, y, z, CraftItemStack.asNMSCopy(new ItemStack(org.bukkit.Material.LINGERING_POTION, 1))); + } else { + entity = new EntityPotion(world, x, y, z, CraftItemStack.asNMSCopy(new ItemStack(org.bukkit.Material.SPLASH_POTION, 1))); + } + } else if (Fireball.class.isAssignableFrom(clazz)) { + if (SmallFireball.class.isAssignableFrom(clazz)) { + entity = new EntitySmallFireball(world); + } else if (WitherSkull.class.isAssignableFrom(clazz)) { + entity = new EntityWitherSkull(world); + } else if (DragonFireball.class.isAssignableFrom(clazz)) { + entity = new EntityDragonFireball(world); + } else { + entity = new EntityLargeFireball(world); + } + entity.setLocationAndAngles(x, y, z, yaw, pitch); + Vector direction = location.getDirection().multiply(10); + ((EntityFireball) entity).setDirection(direction.getX(), direction.getY(), direction.getZ()); + } else if (ShulkerBullet.class.isAssignableFrom(clazz)) { + entity = new EntityShulkerBullet(world); + entity.setLocationAndAngles(x, y, z, yaw, pitch); + } else if (LlamaSpit.class.isAssignableFrom(clazz)) { + entity = new EntityLlamaSpit(world); + entity.setLocationAndAngles(x, y, z, yaw, pitch); + } + } else if (Minecart.class.isAssignableFrom(clazz)) { + if (PoweredMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartFurnace(world, x, y, z); + } else if (StorageMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartChest(world, x, y, z); + } else if (ExplosiveMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartTNT(world, x, y, z); + } else if (HopperMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartHopper(world, x, y, z); + } else if (SpawnerMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartMobSpawner(world, x, y, z); + } else if (CommandMinecart.class.isAssignableFrom(clazz)) { + entity = new EntityMinecartCommandBlock(world, x, y, z); + } else { // Default to rideable minecart for pre-rideable compatibility + entity = new EntityMinecartEmpty(world, x, y, z); + } + } else if (EnderSignal.class.isAssignableFrom(clazz)) { + entity = new EntityEnderEye(world, x, y, z); + } else if (EnderCrystal.class.isAssignableFrom(clazz)) { + entity = new EntityEnderCrystal(world); + entity.setLocationAndAngles(x, y, z, 0, 0); + } else if (LivingEntity.class.isAssignableFrom(clazz)) { + if (Chicken.class.isAssignableFrom(clazz)) { + entity = new EntityChicken(world); + } else if (Cow.class.isAssignableFrom(clazz)) { + if (MushroomCow.class.isAssignableFrom(clazz)) { + entity = new EntityMooshroom(world); + } else { + entity = new EntityCow(world); + } + } else if (Golem.class.isAssignableFrom(clazz)) { + if (Snowman.class.isAssignableFrom(clazz)) { + entity = new EntitySnowman(world); + } else if (IronGolem.class.isAssignableFrom(clazz)) { + entity = new EntityIronGolem(world); + } else if (Shulker.class.isAssignableFrom(clazz)) { + entity = new EntityShulker(world); + } + } else if (Creeper.class.isAssignableFrom(clazz)) { + entity = new EntityCreeper(world); + } else if (Ghast.class.isAssignableFrom(clazz)) { + entity = new EntityGhast(world); + } else if (Pig.class.isAssignableFrom(clazz)) { + entity = new EntityPig(world); + } else if (Player.class.isAssignableFrom(clazz)) { + // need a net server handler for this one + } else if (Sheep.class.isAssignableFrom(clazz)) { + entity = new EntitySheep(world); + } else if (AbstractHorse.class.isAssignableFrom(clazz)) { + if (ChestedHorse.class.isAssignableFrom(clazz)) { + if (Donkey.class.isAssignableFrom(clazz)) { + entity = new EntityDonkey(world); + } else if (Mule.class.isAssignableFrom(clazz)) { + entity = new EntityMule(world); + } else if (Llama.class.isAssignableFrom(clazz)) { + entity = new EntityLlama(world); + } + } else if (SkeletonHorse.class.isAssignableFrom(clazz)) { + entity = new EntitySkeletonHorse(world); + } else if (ZombieHorse.class.isAssignableFrom(clazz)) { + entity = new EntityZombieHorse(world); + } else { + entity = new EntityHorse(world); + } + } else if (Skeleton.class.isAssignableFrom(clazz)) { + if (Stray.class.isAssignableFrom(clazz)){ + entity = new EntityStray(world); + } else if (WitherSkeleton.class.isAssignableFrom(clazz)) { + entity = new EntityWitherSkeleton(world); + } else { + entity = new EntitySkeleton(world); + } + } else if (Slime.class.isAssignableFrom(clazz)) { + if (MagmaCube.class.isAssignableFrom(clazz)) { + entity = new EntityMagmaCube(world); + } else { + entity = new EntitySlime(world); + } + } else if (Spider.class.isAssignableFrom(clazz)) { + if (CaveSpider.class.isAssignableFrom(clazz)) { + entity = new EntityCaveSpider(world); + } else { + entity = new EntitySpider(world); + } + } else if (Squid.class.isAssignableFrom(clazz)) { + entity = new EntitySquid(world); + } else if (Tameable.class.isAssignableFrom(clazz)) { + if (Wolf.class.isAssignableFrom(clazz)) { + entity = new EntityWolf(world); + } else if (Ocelot.class.isAssignableFrom(clazz)) { + entity = new EntityOcelot(world); + } else if (Parrot.class.isAssignableFrom(clazz)) { + entity = new EntityParrot(world); + } + } else if (PigZombie.class.isAssignableFrom(clazz)) { + entity = new EntityPigZombie(world); + } else if (Zombie.class.isAssignableFrom(clazz)) { + if (Husk.class.isAssignableFrom(clazz)) { + entity = new EntityHusk(world); + } else if (ZombieVillager.class.isAssignableFrom(clazz)) { + entity = new EntityZombieVillager(world); + } else { + entity = new EntityZombie(world); + } + } else if (Giant.class.isAssignableFrom(clazz)) { + entity = new EntityGiantZombie(world); + } else if (Silverfish.class.isAssignableFrom(clazz)) { + entity = new EntitySilverfish(world); + } else if (Enderman.class.isAssignableFrom(clazz)) { + entity = new EntityEnderman(world); + } else if (Blaze.class.isAssignableFrom(clazz)) { + entity = new EntityBlaze(world); + } else if (Villager.class.isAssignableFrom(clazz)) { + entity = new EntityVillager(world); + } else if (Witch.class.isAssignableFrom(clazz)) { + entity = new EntityWitch(world); + } else if (Wither.class.isAssignableFrom(clazz)) { + entity = new EntityWither(world); + } else if (ComplexLivingEntity.class.isAssignableFrom(clazz)) { + if (EnderDragon.class.isAssignableFrom(clazz)) { + entity = new EntityDragon(world); + } + } else if (Ambient.class.isAssignableFrom(clazz)) { + if (Bat.class.isAssignableFrom(clazz)) { + entity = new EntityBat(world); + } + } else if (Rabbit.class.isAssignableFrom(clazz)) { + entity = new EntityRabbit(world); + } else if (Endermite.class.isAssignableFrom(clazz)) { + entity = new EntityEndermite(world); + } else if (Guardian.class.isAssignableFrom(clazz)) { + if (ElderGuardian.class.isAssignableFrom(clazz)){ + entity = new EntityElderGuardian(world); + } else { + entity = new EntityGuardian(world); + } + } else if (ArmorStand.class.isAssignableFrom(clazz)) { + entity = new EntityArmorStand(world, x, y, z); + } else if (PolarBear.class.isAssignableFrom(clazz)) { + entity = new EntityPolarBear(world); + } else if (Vex.class.isAssignableFrom(clazz)) { + entity = new EntityVex(world); + } else if (Illager.class.isAssignableFrom(clazz)) { + if (Spellcaster.class.isAssignableFrom(clazz)) { + if (Evoker.class.isAssignableFrom(clazz)) { + entity = new EntityEvoker(world); + } else if (Illusioner.class.isAssignableFrom(clazz)) { + entity = new EntityIllusionIllager(world); + } + } else if (Vindicator.class.isAssignableFrom(clazz)) { + entity = new EntityVindicator(world); + } + } + + if (entity != null) { + entity.setPositionAndRotation(x, y, z, yaw, pitch); + entity.setRotationYawHead(yaw); // SPIGOT-3587 + } + } else if (Hanging.class.isAssignableFrom(clazz)) { + Block block = getBlockAt(location); + BlockFace face = BlockFace.SELF; + + int width = 16; // 1 full block, also painting smallest size. + int height = 16; // 1 full block, also painting smallest size. + + if (ItemFrame.class.isAssignableFrom(clazz)) { + width = 12; + height = 12; + } else if (LeashHitch.class.isAssignableFrom(clazz)) { + width = 9; + height = 9; + } + + BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH}; + final BlockPos pos = new BlockPos((int) x, (int) y, (int) z); + for (BlockFace dir : faces) { + net.minecraft.block.Block nmsBlock = CraftMagicNumbers.getBlock(block.getRelative(dir)); + if (nmsBlock.getDefaultState().getMaterial().isSolid() || BlockRedstoneDiode.isDiode(nmsBlock.getDefaultState())) { + boolean taken = false; + AxisAlignedBB bb = EntityHanging.calculateBoundingBox(null, pos, CraftBlock.blockFaceToNotch(dir).getOpposite(), width, height); + List list = (List) world.getEntitiesWithinAABB(null, bb); + for (Iterator it = list.iterator(); !taken && it.hasNext();) { + net.minecraft.entity.Entity e = it.next(); + if (e instanceof EntityHanging) { + taken = true; // Hanging entities do not like hanging entities which intersect them. + } + } + + if (!taken) { + face = dir; + break; + } + } + } + + if (LeashHitch.class.isAssignableFrom(clazz)) { + entity = new EntityLeashKnot(world, new BlockPos((int) x, (int) y, (int) z)); + entity.forceSpawn = true; + } else { + // No valid face found + Preconditions.checkArgument(face != BlockFace.SELF, "Cannot spawn hanging entity for %s at %s (no free face)", clazz.getName(), location); + + EnumFacing dir = CraftBlock.blockFaceToNotch(face).getOpposite(); + if (Painting.class.isAssignableFrom(clazz)) { + entity = new EntityPainting(world, new BlockPos((int) x, (int) y, (int) z), dir); + } else if (ItemFrame.class.isAssignableFrom(clazz)) { + entity = new EntityItemFrame(world, new BlockPos((int) x, (int) y, (int) z), dir); + } + } + + if (entity != null && !((EntityHanging) entity).onValidSurface()) { + throw new IllegalArgumentException("Cannot spawn hanging entity for " + clazz.getName() + " at " + location); + } + } else if (TNTPrimed.class.isAssignableFrom(clazz)) { + entity = new EntityTNTPrimed(world, x, y, z, null); + } else if (ExperienceOrb.class.isAssignableFrom(clazz)) { + entity = new EntityXPOrb(world, x, y, z, 0); + } else if (Weather.class.isAssignableFrom(clazz)) { + // not sure what this can do + if (LightningStrike.class.isAssignableFrom(clazz)) { + entity = new EntityLightningBolt(world, x, y, z, false); + // what is this, I don't even + } + } else if (Firework.class.isAssignableFrom(clazz)) { + entity = new EntityFireworkRocket(world, x, y, z, net.minecraft.item.ItemStack.EMPTY); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { + entity = new EntityAreaEffectCloud(world, x, y, z); + } else if (EvokerFangs.class.isAssignableFrom(clazz)) { + entity = new EntityEvokerFangs(world, x, y, z, (float) Math.toRadians(yaw), 0, null); + } + + if (entity != null) { + return entity; + } + + throw new IllegalArgumentException("Cannot spawn an entity for " + clazz.getName()); + } + + @SuppressWarnings("unchecked") + public T addEntity(net.minecraft.entity.Entity entity, SpawnReason reason) throws IllegalArgumentException { + return addEntity(entity, reason, null); + } + + @SuppressWarnings("unchecked") + public T addEntity(net.minecraft.entity.Entity entity, SpawnReason reason, Consumer function) throws IllegalArgumentException { + Preconditions.checkArgument(entity != null, "Cannot spawn null entity"); + + if (entity instanceof EntityLiving) { + ((EntityLiving) entity).onInitialSpawn(getHandle().getDifficultyForLocation(new BlockPos(entity)), null); + } + + if (function != null) { + function.accept((T) entity.getBukkitEntity()); + } + + world.spawnEntity(entity, reason); + return (T) entity.getBukkitEntity(); + } + + public T spawn(Location location, Class clazz, Consumer function, SpawnReason reason) throws IllegalArgumentException { + net.minecraft.entity.Entity entity = createEntity(location, clazz); + + return addEntity(entity, reason, function); + } + + public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTempRain) { + return CraftChunk.getEmptyChunkSnapshot(x, z, this, includeBiome, includeBiomeTempRain); + } + + public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) { + world.setAllowedSpawnTypes(allowMonsters, allowAnimals); + } + + public boolean getAllowAnimals() { + return world.spawnPeacefulMobs; + } + + public boolean getAllowMonsters() { + return world.spawnHostileMobs; + } + + public int getMaxHeight() { + return world.getHeight(); + } + + public int getSeaLevel() { + return world.getSeaLevel(); + } + + public boolean getKeepSpawnInMemory() { + return world.keepSpawnInMemory; + } + + public void setKeepSpawnInMemory(boolean keepLoaded) { + world.keepSpawnInMemory = keepLoaded; + // Grab the worlds spawn chunk + BlockPos chunkcoordinates = this.world.getSpawnPoint(); + int chunkCoordX = chunkcoordinates.getX() >> 4; + int chunkCoordZ = chunkcoordinates.getZ() >> 4; + // Cycle through the 25x25 Chunks around it to load/unload the chunks. + for (int x = -12; x <= 12; x++) { + for (int z = -12; z <= 12; z++) { + if (keepLoaded) { + loadChunk(chunkCoordX + x, chunkCoordZ + z); + } else { + if (isChunkLoaded(chunkCoordX + x, chunkCoordZ + z)) { + unloadChunk(chunkCoordX + x, chunkCoordZ + z); + } + } + } + } + } + + @Override + public int hashCode() { + return getUID().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final CraftWorld other = (CraftWorld) obj; + + return this.getUID() == other.getUID(); + } + + public File getWorldFolder() { + return world.getSaveHandler().getWorldDirectory(); + } + + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message); + + for (Player player : getPlayers()) { + player.sendPluginMessage(source, channel, message); + } + } + + public Set getListeningPluginChannels() { + Set result = new HashSet(); + + for (Player player : getPlayers()) { + result.addAll(player.getListeningPluginChannels()); + } + + return result; + } + + public org.bukkit.WorldType getWorldType() { + return org.bukkit.WorldType.getByName(world.getWorldInfo().getTerrainType().getName()); + } + + public boolean canGenerateStructures() { + return world.getWorldInfo().isMapFeaturesEnabled(); + } + + public long getTicksPerAnimalSpawns() { + return world.ticksPerAnimalSpawns; + } + + public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { + world.ticksPerAnimalSpawns = ticksPerAnimalSpawns; + } + + public long getTicksPerMonsterSpawns() { + return world.ticksPerMonsterSpawns; + } + + public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { + world.ticksPerMonsterSpawns = ticksPerMonsterSpawns; + } + + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + server.getWorldMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + public List getMetadata(String metadataKey) { + return server.getWorldMetadata().getMetadata(this, metadataKey); + } + + public boolean hasMetadata(String metadataKey) { + return server.getWorldMetadata().hasMetadata(this, metadataKey); + } + + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + server.getWorldMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + + public int getMonsterSpawnLimit() { + if (monsterSpawn < 0) { + return server.getMonsterSpawnLimit(); + } + + return monsterSpawn; + } + + public void setMonsterSpawnLimit(int limit) { + monsterSpawn = limit; + } + + public int getAnimalSpawnLimit() { + if (animalSpawn < 0) { + return server.getAnimalSpawnLimit(); + } + + return animalSpawn; + } + + public void setAnimalSpawnLimit(int limit) { + animalSpawn = limit; + } + + public int getWaterAnimalSpawnLimit() { + if (waterAnimalSpawn < 0) { + return server.getWaterAnimalSpawnLimit(); + } + + return waterAnimalSpawn; + } + + public void setWaterAnimalSpawnLimit(int limit) { + waterAnimalSpawn = limit; + } + + public int getAmbientSpawnLimit() { + if (ambientSpawn < 0) { + return server.getAmbientSpawnLimit(); + } + + return ambientSpawn; + } + + public void setAmbientSpawnLimit(int limit) { + ambientSpawn = limit; + } + + public void playSound(Location loc, Sound sound, float volume, float pitch) { + playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); + } + + public void playSound(Location loc, String sound, float volume, float pitch) { + playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); + } + + @Override + public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch) { + if (loc == null || sound == null || category == null) { + return; + } + + double x = loc.getX(); + double y = loc.getY(); + double z = loc.getZ(); + + getHandle().playSound(null, x, y, z, CraftSound.getSoundEffect(CraftSound.getSound(sound)), SoundCategory.valueOf(category.name()), volume, pitch); // PAIL: rename + } + + @Override + public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch) { + if (loc == null || sound == null || category == null) { + return; + } + + double x = loc.getX(); + double y = loc.getY(); + double z = loc.getZ(); + + SPacketCustomSound packet = new SPacketCustomSound(sound, SoundCategory.valueOf(category.name()), x, y, z, volume, pitch); + world.getMinecraftServer().getPlayerList().sendToAllNearExcept(null, x, y, z, volume > 1.0F ? 16.0F * volume : 16.0D, this.world, packet); // Paper - this.world.dimension -> this.world + } + + public String getGameRuleValue(String rule) { + return getHandle().getGameRules().getString(rule); + } + + public boolean setGameRuleValue(String rule, String value) { + // No null values allowed + if (rule == null || value == null) { + return false; + } + + if (!isGameRule(rule)) { + return false; + } + + getHandle().getGameRules().setOrCreateGameRule(rule, value); + return true; + } + + public String[] getGameRules() { + return getHandle().getGameRules().getRules(); + } + + public boolean isGameRule(String rule) { + return getHandle().getGameRules().hasRule(rule); + } + + @Override + public WorldBorder getWorldBorder() { + if (this.worldBorder == null) { + this.worldBorder = new CraftWorldBorder(this); + } + + return this.worldBorder; + } + + @Override + public void spawnParticle(Particle particle, Location location, int count) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count) { + spawnParticle(particle, x, y, z, count, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { + spawnParticle(particle, x, y, z, count, 0, 0, 0, data); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, 1, data); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } + getHandle().sendParticles( + null, // Sender + CraftParticle.toNMS(particle), // Particle + true, // Extended range + x, y, z, // Position + count, // Count + offsetX, offsetY, offsetZ, // Random offset + extra, // Speed? + CraftParticle.toData(particle, data) + + ); + + } + + public void processChunkGC() { + chunkGCTickCount++; + + if (chunkLoadCount >= server.chunkGCLoadThresh && server.chunkGCLoadThresh > 0) { + chunkLoadCount = 0; + } else if (chunkGCTickCount >= server.chunkGCPeriod && server.chunkGCPeriod > 0) { + chunkGCTickCount = 0; + } else { + return; + } + + ChunkProviderServer cps = world.getChunkProvider(); + for (net.minecraft.world.chunk.Chunk chunk : cps.id2ChunkMap.values()) { + // If in use, skip it + if (isChunkInUse(chunk.x, chunk.z)) { + continue; + } + + // Already unloading? + if (cps.droppedChunksSet.contains(ChunkPos.asLong(chunk.x, chunk.z))) { + continue; + } + + // Add unload request + cps.queueUnload(chunk); + } + } + // Spigot start + private final Spigot spigot = new Spigot() + { + @Override + public void playEffect( Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius ) + { + Validate.notNull( location, "Location cannot be null" ); + Validate.notNull( effect, "Effect cannot be null" ); + Validate.notNull( location.getWorld(), "World cannot be null" ); + Packet packet; + if ( effect.getType() != Effect.Type.PARTICLE ) + { + int packetData = effect.getId(); + packet = new SPacketEffect( packetData, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ() ), id, false ); + } else + { + net.minecraft.util.EnumParticleTypes particle = null; + int[] extra = null; + for ( net.minecraft.util.EnumParticleTypes p : net.minecraft.util.EnumParticleTypes.values() ) + { + if ( effect.getName().startsWith( p.getParticleName().replace("_", "") ) ) + { + particle = p; + if ( effect.getData() != null ) + { + if ( effect.getData().equals( org.bukkit.Material.class ) ) + { + extra = new int[]{ id }; + } else + { + extra = new int[]{ (data << 12) | (id & 0xFFF) }; + } + } + break; + } + } + if ( extra == null ) + { + extra = new int[0]; + } + packet = new SPacketParticles( particle, true, (float) location.getX(), (float) location.getY(), (float) location.getZ(), offsetX, offsetY, offsetZ, speed, particleCount, extra ); + } + int distance; + radius *= radius; + for ( Player player : getPlayers() ) + { + if ( ( (CraftPlayer) player ).getHandle().connection == null ) + { + continue; + } + if ( !location.getWorld().equals( player.getWorld() ) ) + { + continue; + } + distance = (int) player.getLocation().distanceSquared( location ); + if ( distance <= radius ) + { + ( (CraftPlayer) player ).getHandle().connection.sendPacket( packet ); + } + } + } + + @Override + public void playEffect( Location location, Effect effect ) + { + CraftWorld.this.playEffect( location, effect, 0 ); + } + + @Override + public LightningStrike strikeLightning(Location loc, boolean isSilent) + { + EntityLightningBolt lightning = new EntityLightningBolt( world, loc.getX(), loc.getY(), loc.getZ(), false, isSilent ); + world.addWeatherEffect( lightning ); + return new CraftLightningStrike( server, lightning ); + } + + @Override + public LightningStrike strikeLightningEffect(Location loc, boolean isSilent) + { + EntityLightningBolt lightning = new EntityLightningBolt( world, loc.getX(), loc.getY(), loc.getZ(), true, isSilent ); + world.addWeatherEffect( lightning ); + return new CraftLightningStrike( server, lightning ); + } + }; + + public Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorldBorder.java b/src/main/java/org/bukkit/craftbukkit/CraftWorldBorder.java new file mode 100644 index 00000000..773ea2d8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorldBorder.java @@ -0,0 +1,120 @@ +package org.bukkit.craftbukkit; + +import com.google.common.base.Preconditions; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.WorldBorder; + +public class CraftWorldBorder implements WorldBorder { + + private final World world; + private final net.minecraft.world.border.WorldBorder handle; + + public CraftWorldBorder(CraftWorld world) { + this.world = world; + this.handle = world.getHandle().getWorldBorder(); + } + + @Override + public void reset() { + this.setSize(6.0E7D); + this.setDamageAmount(0.2D); + this.setDamageBuffer(5.0D); + this.setWarningDistance(5); + this.setWarningTime(15); + this.setCenter(0, 0); + } + + @Override + public double getSize() { + return this.handle.getSize(); + } + + @Override + public void setSize(double newSize) { + this.setSize(newSize, 0L); + } + + @Override + public void setSize(double newSize, long time) { + // PAIL: TODO: Magic Values + newSize = Math.min(6.0E7D, Math.max(1.0D, newSize)); + time = Math.min(9223372036854775L, Math.max(0L, time)); + + if (time > 0L) { + this.handle.setTransition(this.handle.getSize(), newSize, time * 1000L); + } else { + this.handle.setTransition(newSize); + } + } + + @Override + public Location getCenter() { + double x = this.handle.getCenterX(); + double z = this.handle.getCenterZ(); + + return new Location(this.world, x, 0, z); + } + + @Override + public void setCenter(double x, double z) { + // PAIL: TODO: Magic Values + x = Math.min(3.0E7D, Math.max(-3.0E7D, x)); + z = Math.min(3.0E7D, Math.max(-3.0E7D, z)); + + this.handle.setCenter(x, z); + } + + @Override + public void setCenter(Location location) { + this.setCenter(location.getX(), location.getZ()); + } + + @Override + public double getDamageBuffer() { + return this.handle.getDamageBuffer(); + } + + @Override + public void setDamageBuffer(double blocks) { + this.handle.setDamageBuffer(blocks); + } + + @Override + public double getDamageAmount() { + return this.handle.getDamageAmount(); + } + + @Override + public void setDamageAmount(double damage) { + this.handle.setDamageAmount(damage); + } + + @Override + public int getWarningTime() { + return this.handle.getWarningTime(); + } + + @Override + public void setWarningTime(int time) { + this.handle.setWarningTime(time); + } + + @Override + public int getWarningDistance() { + return this.handle.getWarningDistance(); + } + + @Override + public void setWarningDistance(int distance) { + this.handle.setWarningDistance(distance); + } + + @Override + public boolean isInside(Location location) { + Preconditions.checkArgument(location != null, "location"); + + return location.getWorld().equals(this.world) && this.handle.contains(new BlockPos(location.getX(), location.getY(), location.getZ())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/LoggerOutputStream.java b/src/main/java/org/bukkit/craftbukkit/LoggerOutputStream.java new file mode 100644 index 00000000..93526ab6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/LoggerOutputStream.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; + +public class LoggerOutputStream extends ByteArrayOutputStream { + private final String separator = System.getProperty("line.separator"); + private final Logger logger; + private final Level level; + + public LoggerOutputStream(Logger logger, Level level) { + super(); + this.logger = logger; + this.level = level; + } + + @Override + public void flush() throws IOException { + synchronized (this) { + super.flush(); + String record = this.toString(); + super.reset(); + + if ((record.length() > 0) && (!record.equals(separator))) { + logger.log(level, record); + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java new file mode 100644 index 00000000..355c9348 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -0,0 +1,197 @@ +package org.bukkit.craftbukkit; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import jline.UnsupportedTerminal; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import org.fusesource.jansi.AnsiConsole; +//import org.fusesource.jansi.AnsiConsole; + +public class Main { + public static boolean useJline = true; + public static boolean useConsole = true; + + public static OptionSet main(String[] args) { + // Todo: Installation script + OptionParser parser = new OptionParser() { + { + acceptsAll(asList("?", "help"), "Show the help"); + + acceptsAll(asList("c", "config"), "Properties file to use") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("server.properties")) + .describedAs("Properties file"); + + acceptsAll(asList("P", "plugins"), "Plugin directory to use") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("plugins")) + .describedAs("Plugin directory"); + + acceptsAll(asList("h", "host", "server-ip"), "Host to listen on") + .withRequiredArg() + .ofType(String.class) + .describedAs("Hostname or IP"); + + acceptsAll(asList("W", "world-dir", "universe", "world-container"), "World container") + .withRequiredArg() + .ofType(File.class) + .describedAs("Directory containing worlds"); + + acceptsAll(asList("w", "world", "level-name"), "World name") + .withRequiredArg() + .ofType(String.class) + .describedAs("World name"); + + acceptsAll(asList("p", "port", "server-port"), "Port to listen on") + .withRequiredArg() + .ofType(Integer.class) + .describedAs("Port"); + + acceptsAll(asList("o", "online-mode"), "Whether to use online authentication") + .withRequiredArg() + .ofType(Boolean.class) + .describedAs("Authentication"); + + acceptsAll(asList("s", "size", "max-players"), "Maximum amount of players") + .withRequiredArg() + .ofType(Integer.class) + .describedAs("Server size"); + + acceptsAll(asList("d", "date-format"), "Format of the date to display in the console (for log entries)") + .withRequiredArg() + .ofType(SimpleDateFormat.class) + .describedAs("Log date format"); + + acceptsAll(asList("log-pattern"), "Specfies the log filename pattern") + .withRequiredArg() + .ofType(String.class) + .defaultsTo("server.log") + .describedAs("Log filename"); + + acceptsAll(asList("log-limit"), "Limits the maximum size of the log file (0 = unlimited)") + .withRequiredArg() + .ofType(Integer.class) + .defaultsTo(0) + .describedAs("Max log size"); + + acceptsAll(asList("log-count"), "Specified how many log files to cycle through") + .withRequiredArg() + .ofType(Integer.class) + .defaultsTo(1) + .describedAs("Log count"); + + acceptsAll(asList("log-append"), "Whether to append to the log file") + .withRequiredArg() + .ofType(Boolean.class) + .defaultsTo(true) + .describedAs("Log append"); + + acceptsAll(asList("log-strip-color"), "Strips color codes from log file"); + + acceptsAll(asList("b", "bukkit-settings"), "File for bukkit settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("bukkit.yml")) + .describedAs("Yml file"); + + acceptsAll(asList("C", "commands-settings"), "File for command settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("commands.yml")) + .describedAs("Yml file"); + + acceptsAll(asList("nojline"), "Disables jline and emulates the vanilla console"); + + acceptsAll(asList("noconsole"), "Disables the console"); + + acceptsAll(asList("v", "version"), "Show the CraftBukkit Version"); + + acceptsAll(asList("demo"), "Demo mode"); + + acceptsAll(asList("mixin"), "This argument is needed for proper Mixin Framework work in the test env"); + + // Spigot Start + acceptsAll(asList("S", "spigot-settings"), "File for spigot settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("spigot.yml")) + .describedAs("Yml file"); + // Spigot End + + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() + .ofType(String.class) + .defaultsTo("Unknown Server") + .describedAs("Name"); + // Paper end + } + }; + + OptionSet options = null; + + try { + options = parser.parse(args); + } catch (joptsimple.OptionException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, ex.getLocalizedMessage()); + } + + if ((options == null) || (options.has("?"))) { + try { + parser.printHelpOn(System.out); + } catch (IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + // Do you love Java using + and ! as string based identifiers? I sure do! + String path = new File(".").getAbsolutePath(); + if (path.contains("!") || path.contains("+")) { + System.err.println("Cannot run server in a directory with ! or + in the pathname. Please rename the affected folders and try again."); + return null; + } + + try { + // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals + String jline_UnsupportedTerminal = new String(new char[] {'j','l','i','n','e','.','U','n','s','u','p','p','o','r','t','e','d','T','e','r','m','i','n','a','l'}); + String jline_terminal = new String(new char[] {'j','l','i','n','e','.','t','e','r','m','i','n','a','l'}); + + useJline = !(jline_UnsupportedTerminal).equals(System.getProperty(jline_terminal)); + + if (options.has("nojline")) { + System.setProperty("user.language", "en"); + useJline = false; + } + if (Main.useJline) { + AnsiConsole.systemInstall(); + } + else { + System.setProperty("jline.terminal", UnsupportedTerminal.class.getName()); + } + + + if (options.has("noconsole")) { + useConsole = false; + } + } catch (Throwable t) { + t.printStackTrace(); + } + return options; + } + return null; + } + + private static List asList(String... params) { + return Arrays.asList(params); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/Overridden.java b/src/main/java/org/bukkit/craftbukkit/Overridden.java new file mode 100644 index 00000000..1c19c69f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/Overridden.java @@ -0,0 +1,14 @@ +package org.bukkit.craftbukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a method needs to be overridden in sub classes + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Overridden { +} diff --git a/src/main/java/org/bukkit/craftbukkit/TrigMath.java b/src/main/java/org/bukkit/craftbukkit/TrigMath.java new file mode 100644 index 00000000..6d613c53 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/TrigMath.java @@ -0,0 +1,47 @@ +package org.bukkit.craftbukkit; + +/** + * Credits for this class goes to user aioobe on stackoverflow.com + * Source: http://stackoverflow.com/questions/4454630/j2me-calculate-the-the-distance-between-2-latitude-and-longitude + */ +public class TrigMath { + + static final double sq2p1 = 2.414213562373095048802e0; + static final double sq2m1 = .414213562373095048802e0; + static final double p4 = .161536412982230228262e2; + static final double p3 = .26842548195503973794141e3; + static final double p2 = .11530293515404850115428136e4; + static final double p1 = .178040631643319697105464587e4; + static final double p0 = .89678597403663861959987488e3; + static final double q4 = .5895697050844462222791e2; + static final double q3 = .536265374031215315104235e3; + static final double q2 = .16667838148816337184521798e4; + static final double q1 = .207933497444540981287275926e4; + static final double q0 = .89678597403663861962481162e3; + static final double PIO2 = 1.5707963267948966135E0; + + private static double mxatan(double arg) { + double argsq = arg * arg, value; + + value = ((((p4 * argsq + p3) * argsq + p2) * argsq + p1) * argsq + p0); + value = value / (((((argsq + q4) * argsq + q3) * argsq + q2) * argsq + q1) * argsq + q0); + return value * arg; + } + + private static double msatan(double arg) { + return arg < sq2m1 ? mxatan(arg) + : arg > sq2p1 ? PIO2 - mxatan(1 / arg) + : PIO2 / 2 + mxatan((arg - 1) / (arg + 1)); + } + + public static double atan(double arg) { + return arg > 0 ? msatan(arg) : -msatan(-arg); + } + + public static double atan2(double arg1, double arg2) { + if (arg1 + arg2 == arg1) + return arg1 >= 0 ? PIO2 : -PIO2; + arg1 = atan(arg1 / arg2); + return arg2 < 0 ? arg1 <= 0 ? arg1 + Math.PI : arg1 - Math.PI : arg1; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java new file mode 100644 index 00000000..d6b17e36 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java @@ -0,0 +1,30 @@ +package org.bukkit.craftbukkit.advancement; + +import java.util.Collection; +import java.util.Collections; +import net.minecraft.advancements.Advancement; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; + +public class CraftAdvancement implements org.bukkit.advancement.Advancement { + + private final Advancement handle; + + public CraftAdvancement(Advancement handle) { + this.handle = handle; + } + + public Advancement getHandle() { + return handle; + } + + @Override + public NamespacedKey getKey() { + return CraftNamespacedKey.fromMinecraft(handle.getId()); + } + + @Override + public Collection getCriteria() { + return Collections.unmodifiableCollection(handle.getCriteria().keySet()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementProgress.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementProgress.java new file mode 100644 index 00000000..a3261cb6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementProgress.java @@ -0,0 +1,60 @@ +package org.bukkit.craftbukkit.advancement; + +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +import net.minecraft.advancements.CriterionProgress; +import net.minecraft.advancements.PlayerAdvancements; +import org.bukkit.advancement.Advancement; +import org.bukkit.advancement.AdvancementProgress; + +public class CraftAdvancementProgress implements AdvancementProgress { + + private final CraftAdvancement advancement; + private final PlayerAdvancements playerData; + private final net.minecraft.advancements.AdvancementProgress handle; + + public CraftAdvancementProgress(CraftAdvancement advancement, PlayerAdvancements player, net.minecraft.advancements.AdvancementProgress handle) { + this.advancement = advancement; + this.playerData = player; + this.handle = handle; + } + + @Override + public Advancement getAdvancement() { + return advancement; + } + + @Override + public boolean isDone() { + return handle.isDone(); + } + + @Override + public boolean awardCriteria(String criteria) { + return playerData.grantCriterion(advancement.getHandle(), criteria); + } + + @Override + public boolean revokeCriteria(String criteria) { + return playerData.revokeCriterion(advancement.getHandle(), criteria); + } + + @Override + public Date getDateAwarded(String criteria) { + CriterionProgress criterion = handle.getCriterionProgress(criteria); + return (criterion == null) ? null : criterion.getObtained(); + } + + @Override + public Collection getRemainingCriteria() { + return Collections.unmodifiableCollection(Lists.newArrayList(handle.getRemaningCriteria())); + } + + @Override + public Collection getAwardedCriteria() { + return Collections.unmodifiableCollection(Lists.newArrayList(handle.getCompletedCriteria())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java new file mode 100644 index 00000000..8c1e1ac7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java @@ -0,0 +1,75 @@ +package org.bukkit.craftbukkit.attribute; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; + +public class CraftAttributeInstance implements AttributeInstance { + + private final net.minecraft.entity.ai.attributes.IAttributeInstance handle; + private final Attribute attribute; + + public CraftAttributeInstance(net.minecraft.entity.ai.attributes.IAttributeInstance handle, Attribute attribute) { + this.handle = handle; + this.attribute = attribute; + } + + @Override + public Attribute getAttribute() { + return attribute; + } + + @Override + public double getBaseValue() { + return handle.getBaseValue(); + } + + @Override + public void setBaseValue(double d) { + handle.setBaseValue(d); + } + + @Override + public Collection getModifiers() { + List result = new ArrayList(); + for (net.minecraft.entity.ai.attributes.AttributeModifier nms : handle.getModifiers()) { + result.add(convert(nms)); + } + + return result; + } + + @Override + public void addModifier(AttributeModifier modifier) { + Preconditions.checkArgument(modifier != null, "modifier"); + handle.applyModifier(convert(modifier)); + } + + @Override + public void removeModifier(AttributeModifier modifier) { + Preconditions.checkArgument(modifier != null, "modifier"); + handle.removeModifier(convert(modifier)); + } + + @Override + public double getValue() { + return handle.getAttributeValue(); + } + + @Override + public double getDefaultValue() { + return handle.getAttribute().getDefaultValue(); + } + + private static net.minecraft.entity.ai.attributes.AttributeModifier convert(AttributeModifier bukkit) { + return new net.minecraft.entity.ai.attributes.AttributeModifier(bukkit.getUniqueId(), bukkit.getName(), bukkit.getAmount(), bukkit.getOperation().ordinal()); + } + + private static AttributeModifier convert(net.minecraft.entity.ai.attributes.AttributeModifier nms) { + return new AttributeModifier(nms.getID(), nms.getName(), nms.getAmount(), AttributeModifier.Operation.values()[nms.getOperation()]); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java new file mode 100644 index 00000000..1504df4c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.attribute; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.ai.attributes.AbstractAttributeMap; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; + +public class CraftAttributeMap implements Attributable { + + private final AbstractAttributeMap handle; + + public CraftAttributeMap(AbstractAttributeMap handle) { + this.handle = handle; + } + + @Override + public AttributeInstance getAttribute(Attribute attribute) { + Preconditions.checkArgument(attribute != null, "attribute"); + net.minecraft.entity.ai.attributes.IAttributeInstance nms = handle.getAttributeInstanceByName(toMinecraft(attribute.name())); + + return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); + } + + static String toMinecraft(String bukkit) { + int first = bukkit.indexOf('_'); + int second = bukkit.indexOf('_', first + 1); + + StringBuilder sb = new StringBuilder(bukkit.toLowerCase(java.util.Locale.ENGLISH)); + + sb.setCharAt(first, '.'); + if (second != -1) { + sb.deleteCharAt(second); + sb.setCharAt(second, bukkit.charAt(second + 1)); + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java new file mode 100644 index 00000000..efbc6fc5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java @@ -0,0 +1,106 @@ +package org.bukkit.craftbukkit.block; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.EnumDyeColor; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.tileentity.TileEntityBanner; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.Banner; +import org.bukkit.block.Block; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; + +public class CraftBanner extends CraftBlockEntityState implements Banner { + + private DyeColor base; + private List patterns; + + public CraftBanner(final Block block) { + super(block, TileEntityBanner.class); + } + + public CraftBanner(final Material material, final TileEntityBanner te) { + super(material, te); + } + + @Override + public void load(TileEntityBanner banner) { + super.load(banner); + + base = DyeColor.getByDyeData((byte) banner.baseColor.getDyeDamage()); + patterns = new ArrayList(); + + if (banner.patterns != null) { + for (int i = 0; i < banner.patterns.tagCount(); i++) { + NBTTagCompound p = (NBTTagCompound) banner.patterns.get(i); + patterns.add(new Pattern(DyeColor.getByDyeData((byte) p.getInteger("Color")), PatternType.getByIdentifier(p.getString("Pattern")))); + } + } + } + + @Override + public DyeColor getBaseColor() { + return this.base; + } + + @Override + public void setBaseColor(DyeColor color) { + this.base = color; + } + + @Override + public List getPatterns() { + return new ArrayList(patterns); + } + + @Override + public void setPatterns(List patterns) { + this.patterns = new ArrayList(patterns); + } + + @Override + public void addPattern(Pattern pattern) { + this.patterns.add(pattern); + } + + @Override + public Pattern getPattern(int i) { + return this.patterns.get(i); + } + + @Override + public Pattern removePattern(int i) { + return this.patterns.remove(i); + } + + @Override + public void setPattern(int i, Pattern pattern) { + this.patterns.set(i, pattern); + } + + @Override + public int numberOfPatterns() { + return patterns.size(); + } + + @Override + public void applyTo(TileEntityBanner banner) { + super.applyTo(banner); + + banner.baseColor = EnumDyeColor.byDyeDamage(base.getDyeData()); + + NBTTagList newPatterns = new NBTTagList(); + + for (Pattern p : patterns) { + NBTTagCompound compound = new NBTTagCompound(); + compound.setInteger("Color", p.getColor().getDyeData()); + compound.setString("Pattern", p.getPattern().getIdentifier()); + newPatterns.appendTag(compound); + } + banner.patterns = newPatterns; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java new file mode 100644 index 00000000..fd94b473 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java @@ -0,0 +1,98 @@ +package org.bukkit.craftbukkit.block; + +import java.util.ArrayList; +import java.util.Collection; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.potion.Potion; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityBeacon; +import org.bukkit.Material; +import org.bukkit.block.Beacon; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.inventory.CraftInventoryBeacon; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.BeaconInventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class CraftBeacon extends CraftContainer implements Beacon { + + public CraftBeacon(final Block block) { + super(block, TileEntityBeacon.class); + } + + public CraftBeacon(final Material material, final TileEntityBeacon te) { + super(material, te); + } + + @Override + public BeaconInventory getSnapshotInventory() { + return new CraftInventoryBeacon(this.getSnapshot()); + } + + @Override + public BeaconInventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventoryBeacon(this.getTileEntity()); + } + + @Override + public Collection getEntitiesInRange() { + TileEntity tileEntity = this.getTileEntityFromWorld(); + if (tileEntity instanceof TileEntityBeacon) { + TileEntityBeacon beacon = (TileEntityBeacon) tileEntity; + + Collection nms = beacon.getHumansInRange(); + Collection bukkit = new ArrayList(nms.size()); + + for (EntityPlayer human : nms) { + bukkit.add(human.getBukkitEntity()); + } + + return bukkit; + } + + // block is no longer a beacon + return new ArrayList(); + } + + @Override + public int getTier() { + return this.getSnapshot().levels; + } + + @Override + public PotionEffect getPrimaryEffect() { + return this.getSnapshot().getPrimaryEffect(); + } + + @Override + public void setPrimaryEffect(PotionEffectType effect) { + this.getSnapshot().primaryEffect = (effect != null) ? Potion.getPotionById(effect.getId()) : null; + } + + @Override + public PotionEffect getSecondaryEffect() { + return this.getSnapshot().getSecondaryEffect(); + } + + @Override + public void setSecondaryEffect(PotionEffectType effect) { + this.getSnapshot().secondaryEffect = (effect != null) ? Potion.getPotionById(effect.getId()) : null; + } + + @Override + public String getCustomName() { + TileEntityBeacon beacon = this.getSnapshot(); + return beacon.hasCustomName() ? beacon.getName() : null; + } + + @Override + public void setCustomName(String name) { + this.getSnapshot().setName(name); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBed.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBed.java new file mode 100644 index 00000000..8771f65b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBed.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.block; + +import com.google.common.base.Preconditions; +import net.minecraft.item.EnumDyeColor; +import net.minecraft.tileentity.TileEntityBed; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.Bed; +import org.bukkit.block.Block; + +public class CraftBed extends CraftBlockEntityState implements Bed { + + private DyeColor color; + + public CraftBed(Block block) { + super(block, TileEntityBed.class); + } + + public CraftBed(Material material, TileEntityBed te) { + super(material, te); + } + + @Override + public void load(TileEntityBed bed) { + super.load(bed); + + color = DyeColor.getByWoolData((byte) bed.getColor().getMetadata()); + } + + @Override + public DyeColor getColor() { + return color; + } + + @Override + public void setColor(DyeColor color) { + Preconditions.checkArgument(color != null, "color"); + + this.color = color; + } + + @Override + public void applyTo(TileEntityBed bed) { + super.applyTo(bed); + + bed.setColor(EnumDyeColor.byMetadata(color.getWoolData())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java new file mode 100644 index 00000000..ff40f51c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -0,0 +1,568 @@ +package org.bukkit.craftbukkit.block; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import net.minecraft.block.BlockCocoa; +import net.minecraft.block.BlockContainer; +import net.minecraft.block.BlockRedstoneWire; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.Item; +import net.minecraft.nbt.NBTTagCompound; + +import net.minecraft.nbt.NBTUtil; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntitySkull; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.EnumSkyBlock; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.*; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.BlockVector; + +public class CraftBlock implements Block { + private final CraftChunk chunk; + private final int x; + private final int y; + private final int z; + + public CraftBlock(CraftChunk chunk, int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.chunk = chunk; + } + + private net.minecraft.block.Block getNMSBlock() { + return CraftMagicNumbers.getBlock(this); // TODO: UPDATE THIS + } + + private static net.minecraft.block.Block getNMSBlock(int type) { + return CraftMagicNumbers.getBlock(type); + } + + public World getWorld() { + return chunk.getWorld(); + } + + public Location getLocation() { + return new Location(getWorld(), x, y, z); + } + + public Location getLocation(Location loc) { + if (loc != null) { + loc.setWorld(getWorld()); + loc.setX(x); + loc.setY(y); + loc.setZ(z); + loc.setYaw(0); + loc.setPitch(0); + } + + return loc; + } + + public BlockVector getVector() { + return new BlockVector(x, y, z); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public Chunk getChunk() { + return chunk; + } + + public void setData(final byte data) { + setData(data, 3); + } + + public void setData(final byte data, boolean applyPhysics) { + if (applyPhysics) { + setData(data, 3); + } else { + setData(data, 2); + } + } + + private void setData(final byte data, int flag) { + net.minecraft.world.World world = chunk.getHandle().getWorld(); + BlockPos position = new BlockPos(x, y, z); + IBlockState blockData = world.getBlockState(position); + world.setBlockState(position, blockData.getBlock().getStateFromMeta(data), flag); + } + + private IBlockState getData0() { + return chunk.getHandle().getBlockState(new BlockPos(x, y, z)); + } + + public byte getData() { + IBlockState blockData = chunk.getHandle().getBlockState(new BlockPos(x, y, z)); + return (byte) blockData.getBlock().getMetaFromState(blockData); + } + + public void setType(final Material type) { + setType(type, true); + } + + @Override + public void setType(Material type, boolean applyPhysics) { + setTypeId(type.getId(), applyPhysics); + } + + public boolean setTypeId(final int type) { + return setTypeId(type, true); + } + + public boolean setTypeId(final int type, final boolean applyPhysics) { + net.minecraft.block.Block block = getNMSBlock(type); + return setTypeIdAndData(type, (byte) block.getMetaFromState(block.getDefaultState()), applyPhysics); + } + + public boolean setTypeIdAndData(final int type, final byte data, final boolean applyPhysics) { + IBlockState blockData = getNMSBlock(type).getStateFromMeta(data); + BlockPos position = new BlockPos(x, y, z); + + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup + if (type != 0 && blockData.getBlock() instanceof BlockContainer && type != getTypeId()) { + chunk.getHandle().getWorld().setBlockState(position, Blocks.AIR.getDefaultState(), 0); + } + + if (applyPhysics) { + return chunk.getHandle().getWorld().setBlockState(position, blockData, 3); + } else { + IBlockState old = chunk.getHandle().getBlockState(position); + boolean success = chunk.getHandle().getWorld().setBlockState(position, blockData, 18); // NOTIFY | NO_OBSERVER + if (success) { + chunk.getHandle().getWorld().notifyBlockUpdate( + position, + old, + blockData, + 3 + ); + } + return success; + } + } + + public Material getType() { + return Material.getMaterial(getTypeId()); + } + + @Deprecated + @Override + public int getTypeId() { + return CraftMagicNumbers.getId(chunk.getHandle().getBlockState(new BlockPos(this.x, this.y, this.z)).getBlock()); + } + + public byte getLightLevel() { + return (byte) chunk.getHandle().getWorld().getLightFromNeighbors(new BlockPos(this.x, this.y, this.z)); + } + + public byte getLightFromSky() { + return (byte) chunk.getHandle().getWorld().getLightFor(EnumSkyBlock.SKY, new BlockPos(this.x, this.y, this.z)); + } + + public byte getLightFromBlocks() { + return (byte) chunk.getHandle().getWorld().getLightFor(EnumSkyBlock.BLOCK, new BlockPos(this.x, this.y, this.z)); + } + + + public Block getFace(final BlockFace face) { + return getRelative(face, 1); + } + + public Block getFace(final BlockFace face, final int distance) { + return getRelative(face, distance); + } + + public Block getRelative(final int modX, final int modY, final int modZ) { + return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ); + } + + public Block getRelative(BlockFace face) { + return getRelative(face, 1); + } + + public Block getRelative(BlockFace face, int distance) { + return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance); + } + + public BlockFace getFace(final Block block) { + BlockFace[] values = BlockFace.values(); + + for (BlockFace face : values) { + if ((this.getX() + face.getModX() == block.getX()) && + (this.getY() + face.getModY() == block.getY()) && + (this.getZ() + face.getModZ() == block.getZ()) + ) { + return face; + } + } + + return null; + } + + @Override + public String toString() { + return "CraftBlock{" + "chunk=" + chunk + ",x=" + x + ",y=" + y + ",z=" + z + ",type=" + getType() + ",data=" + getData() + '}'; + } + + public static BlockFace notchToBlockFace(EnumFacing notch) { + if (notch == null) return BlockFace.SELF; + switch (notch) { + case DOWN: + return BlockFace.DOWN; + case UP: + return BlockFace.UP; + case NORTH: + return BlockFace.NORTH; + case SOUTH: + return BlockFace.SOUTH; + case WEST: + return BlockFace.WEST; + case EAST: + return BlockFace.EAST; + default: + return BlockFace.SELF; + } + } + + public static EnumFacing blockFaceToNotch(BlockFace face) { + switch (face) { + case DOWN: + return EnumFacing.DOWN; + case UP: + return EnumFacing.UP; + case NORTH: + return EnumFacing.NORTH; + case SOUTH: + return EnumFacing.SOUTH; + case WEST: + return EnumFacing.WEST; + case EAST: + return EnumFacing.EAST; + default: + return null; + } + } + + public BlockState getState() { + Material material = getType(); + + // Kettle start - if null, check for TE that implements IInventory (cauldron stuff) + if (material == null) + { + TileEntity te = ((CraftWorld)this.getWorld()).getHandle().getTileEntity(new BlockPos(this.getX(), this.getY(), this.getZ())); + if (te != null && te instanceof IInventory) + { + // In order to allow plugins to properly grab the container location, we must pass a class that extends CraftBlockState and implements InventoryHolder. + // Note: This will be returned when TileEntity.getOwner() is called + return new CraftCustomContainer(this); + } + // pass default state + return new CraftBlockState(this); + } + // Kettle end + + switch (material) { + case SIGN: + case SIGN_POST: + case WALL_SIGN: + return new CraftSign(this); + case CHEST: + case TRAPPED_CHEST: + return new CraftChest(this); + case BURNING_FURNACE: + case FURNACE: + return new CraftFurnace(this); + case DISPENSER: + return new CraftDispenser(this); + case DROPPER: + return new CraftDropper(this); + case END_GATEWAY: + return new CraftEndGateway(this); + case HOPPER: + return new CraftHopper(this); + case MOB_SPAWNER: + return new CraftCreatureSpawner(this); + case NOTE_BLOCK: + return new CraftNoteBlock(this); + case JUKEBOX: + return new CraftJukebox(this); + case BREWING_STAND: + return new CraftBrewingStand(this); + case SKULL: + return new CraftSkull(this); + case COMMAND: + case COMMAND_CHAIN: + case COMMAND_REPEATING: + return new CraftCommandBlock(this); + case BEACON: + return new CraftBeacon(this); + case BANNER: + case WALL_BANNER: + case STANDING_BANNER: + return new CraftBanner(this); + case FLOWER_POT: + return new CraftFlowerPot(this); + case STRUCTURE_BLOCK: + return new CraftStructureBlock(this); + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + return new CraftShulkerBox(this); + case ENCHANTMENT_TABLE: + return new CraftEnchantingTable(this); + case ENDER_CHEST: + return new CraftEnderChest(this); + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + return new CraftDaylightDetector(this); + case REDSTONE_COMPARATOR_OFF: + case REDSTONE_COMPARATOR_ON: + return new CraftComparator(this); + case BED_BLOCK: + return new CraftBed(this); + default: + TileEntity tileEntity = chunk.getCraftWorld().getTileEntityAt(x, y, z); + if (tileEntity != null) { + // block with unhandled TileEntity: + return new CraftBlockEntityState(this, (Class) tileEntity.getClass()); + } else { + // Block without TileEntity: + return new CraftBlockState(this); + } + } + } + + public Biome getBiome() { + return getWorld().getBiome(x, z); + } + + public void setBiome(Biome bio) { + getWorld().setBiome(x, z, bio); + } + + public static Biome biomeBaseToBiome(net.minecraft.world.biome.Biome base) { + if (base == null) { + return null; + } + + return Biome.valueOf(net.minecraft.world.biome.Biome.REGISTRY.getNameForObject(base).getResourcePath().toUpperCase(java.util.Locale.ENGLISH)); + } + + public static net.minecraft.world.biome.Biome biomeToBiomeBase(Biome bio) { + if (bio == null) { + return null; + } + + return net.minecraft.world.biome.Biome.REGISTRY.getObject(new ResourceLocation(bio.name().toLowerCase(java.util.Locale.ENGLISH))); + } + + public double getTemperature() { + return getWorld().getTemperature(x, z); + } + + public double getHumidity() { + return getWorld().getHumidity(x, z); + } + + public boolean isBlockPowered() { + return chunk.getHandle().getWorld().getStrongPower(new BlockPos(x, y, z)) > 0; + } + + public boolean isBlockIndirectlyPowered() { + return chunk.getHandle().getWorld().isBlockPowered(new BlockPos(x, y, z)); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof CraftBlock)) return false; + CraftBlock other = (CraftBlock) o; + + return this.x == other.x && this.y == other.y && this.z == other.z && this.getWorld().equals(other.getWorld()); + } + + @Override + public int hashCode() { + return this.y << 24 ^ this.x ^ this.z ^ this.getWorld().hashCode(); + } + + public boolean isBlockFacePowered(BlockFace face) { + return chunk.getHandle().getWorld().isSidePowered(new BlockPos(x, y, z), blockFaceToNotch(face)); + } + + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { + int power = chunk.getHandle().getWorld().getRedstonePower(new BlockPos(x, y, z), blockFaceToNotch(face)); + + Block relative = getRelative(face); + if (relative.getType() == Material.REDSTONE_WIRE) { + return Math.max(power, relative.getData()) > 0; + } + + return power > 0; + } + + public int getBlockPower(BlockFace face) { + int power = 0; + BlockRedstoneWire wire = Blocks.REDSTONE_WIRE; + net.minecraft.world.World world = chunk.getHandle().getWorld(); + if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x, y - 1, z), EnumFacing.DOWN)) power = wire.getMaxCurrentStrength(world, new BlockPos(x, y - 1, z), power); + if ((face == BlockFace.UP || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x, y + 1, z), EnumFacing.UP)) power = wire.getMaxCurrentStrength(world, new BlockPos(x, y + 1, z), power); + if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x + 1, y, z), EnumFacing.EAST)) power = wire.getMaxCurrentStrength(world, new BlockPos(x + 1, y, z), power); + if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x - 1, y, z), EnumFacing.WEST)) power = wire.getMaxCurrentStrength(world, new BlockPos(x - 1, y, z), power); + if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x, y, z - 1), EnumFacing.NORTH)) power = wire.getMaxCurrentStrength(world, new BlockPos(x, y, z - 1), power); + if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.isSidePowered(new BlockPos(x, y, z + 1), EnumFacing.SOUTH)) power = wire.getMaxCurrentStrength(world, new BlockPos(x, y, z - 1), power); + return power > 0 ? power : (face == BlockFace.SELF ? isBlockIndirectlyPowered() : isBlockFaceIndirectlyPowered(face)) ? 15 : 0; + } + + public int getBlockPower() { + return getBlockPower(BlockFace.SELF); + } + + public boolean isEmpty() { + return getType() == Material.AIR; + } + + public boolean isLiquid() { + return (getType() == Material.WATER) || (getType() == Material.STATIONARY_WATER) || (getType() == Material.LAVA) || (getType() == Material.STATIONARY_LAVA); + } + + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(getNMSBlock().getMobilityFlag(getNMSBlock().getStateFromMeta(getData())).ordinal()); + } + + private boolean itemCausesDrops(ItemStack item) { + net.minecraft.block.Block block = this.getNMSBlock(); + Item itemType = item != null ? Item.getItemById(item.getTypeId()) : null; + return block != null && (block.getDefaultState().getMaterial().isToolNotRequired() || (itemType != null && itemType.canHarvestBlock(block.getDefaultState()))); + } + + public boolean breakNaturally() { + // Order matters here, need to drop before setting to air so skulls can get their data + net.minecraft.block.Block block = this.getNMSBlock(); + byte data = getData(); + boolean result = false; + + if (block != null && block != Blocks.AIR) { + block.dropBlockAsItemWithChance(chunk.getHandle().getWorld(), new BlockPos(x, y, z), block.getStateFromMeta(data), 1.0F, 0); + result = true; + } + + setTypeId(Material.AIR.getId()); + return result; + } + + public boolean breakNaturally(ItemStack item) { + if (itemCausesDrops(item)) { + return breakNaturally(); + } else { + return setTypeId(Material.AIR.getId()); + } + } + + public Collection getDrops() { + List drops = new ArrayList(); + + net.minecraft.block.Block block = this.getNMSBlock(); + if (block != Blocks.AIR) { + IBlockState data = getData0(); + // based on nms.Block.dropNaturally + int count = block.quantityDroppedWithBonus(0, chunk.getHandle().getWorld().rand); + for (int i = 0; i < count; ++i) { + Item item = block.getItemDropped(data, chunk.getHandle().getWorld().rand, 0); + if (item != Items.AIR) { + // Skulls are special, their data is based on the tile entity + if (Blocks.SKULL == block) { + net.minecraft.item.ItemStack nmsStack = new net.minecraft.item.ItemStack(item, 1, block.damageDropped(data)); + TileEntitySkull tileentityskull = (TileEntitySkull) chunk.getHandle().getWorld().getTileEntity(new BlockPos(x, y, z)); + + if (tileentityskull.getSkullType() == 3 && tileentityskull.getPlayerProfile() != null) { + nmsStack.setTagCompound(new NBTTagCompound()); + NBTTagCompound nbttagcompound = new NBTTagCompound(); + + NBTUtil.writeGameProfile(nbttagcompound, tileentityskull.getPlayerProfile()); + nmsStack.getTagCompound().setTag("SkullOwner", nbttagcompound); + } + + drops.add(CraftItemStack.asBukkitCopy(nmsStack)); + // We don't want to drop cocoa blocks, we want to drop cocoa beans. + } else if (Blocks.COCOA == block) { + int age = (Integer) data.getValue(BlockCocoa.AGE); + int dropAmount = (age >= 2 ? 3 : 1); + for (int j = 0; j < dropAmount; ++j) { + drops.add(new ItemStack(Material.INK_SACK, 1, (short) 3)); + } + } else { + drops.add(new ItemStack(CraftMagicNumbers.getMaterial(item), 1, (short) block.damageDropped(data))); + } + } + } + } + return drops; + } + + public Collection getDrops(ItemStack item) { + if (itemCausesDrops(item)) { + return getDrops(); + } else { + return Collections.emptyList(); + } + } + + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + chunk.getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + public List getMetadata(String metadataKey) { + return chunk.getCraftWorld().getBlockMetadata().getMetadata(this, metadataKey); + } + + public boolean hasMetadata(String metadataKey) { + return chunk.getCraftWorld().getBlockMetadata().hasMetadata(this, metadataKey); + } + + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + chunk.getCraftWorld().getBlockMetadata().removeMetadata(this, metadataKey, owningPlugin); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java new file mode 100644 index 00000000..c941c711 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -0,0 +1,121 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftWorld; + +public class CraftBlockEntityState extends CraftBlockState { + + private final Class tileEntityClass; + private final T tileEntity; + private final T snapshot; + + public CraftBlockEntityState(Block block, Class tileEntityClass) { + super(block); + + this.tileEntityClass = tileEntityClass; + + // get tile entity from block: + CraftWorld world = (CraftWorld) this.getWorld(); + this.tileEntity = tileEntityClass.cast(world.getTileEntityAt(this.getX(), this.getY(), this.getZ())); + + // copy tile entity data: + this.snapshot = this.createSnapshot(tileEntity); + this.load(snapshot); + } + + public CraftBlockEntityState(Material material, T tileEntity) { + super(material); + + this.tileEntityClass = (Class) tileEntity.getClass(); + this.tileEntity = tileEntity; + + // copy tile entity data: + this.snapshot = this.createSnapshot(tileEntity); + this.load(snapshot); + } + + private T createSnapshot(T tileEntity) { + if (tileEntity == null) { + return null; + } + + NBTTagCompound nbtTagCompound = tileEntity.writeToNBT(new NBTTagCompound()); + T snapshot = (T) TileEntity.create(tileEntity.getWorld(), nbtTagCompound); + + return snapshot; + } + + // copies the TileEntity-specific data, retains the position + private void copyData(T from, T to) { + BlockPos pos = to.getPos(); + NBTTagCompound nbtTagCompound = from.writeToNBT(new NBTTagCompound()); + to.readFromNBT(nbtTagCompound); + + // reset the original position: + to.setPos(pos); + } + + // gets the wrapped TileEntity + @Override + public T getTileEntity() { // Paper - protected -> public + return tileEntity; + } + + // gets the cloned TileEntity which is used to store the captured data + protected T getSnapshot() { + return snapshot; + } + + // gets the current TileEntity from the world at this position + protected TileEntity getTileEntityFromWorld() { + requirePlaced(); + + return ((CraftWorld) this.getWorld()).getTileEntityAt(this.getX(), this.getY(), this.getZ()); + } + + // gets the NBT data of the TileEntity represented by this block state + public NBTTagCompound getSnapshotNBT() { + // update snapshot + applyTo(snapshot); + + return snapshot.writeToNBT(new NBTTagCompound()); + } + + // copies the data of the given tile entity to this block state + protected void load(T tileEntity) { + if (tileEntity != null && tileEntity != snapshot) { + copyData(tileEntity, snapshot); + } + } + + // applies the TileEntity data of this block state to the given TileEntity + protected void applyTo(T tileEntity) { + if (tileEntity != null && tileEntity != snapshot) { + copyData(snapshot, tileEntity); + } + } + + protected boolean isApplicable(TileEntity tileEntity) { + return tileEntityClass.isInstance(tileEntity); + } + + @Override + public boolean update(boolean force, boolean applyPhysics) { + boolean result = super.update(force, applyPhysics); + + if (result && this.isPlaced()) { + TileEntity tile = getTileEntityFromWorld(); + + if (isApplicable(tile)) { + applyTo(tileEntityClass.cast(tile)); + tile.markDirty(); + } + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java new file mode 100644 index 00000000..0855881d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -0,0 +1,334 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.BlockSnapshot; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.material.Attachable; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +import java.util.List; + +public class CraftBlockState implements BlockState { + private final CraftWorld world; + private final CraftChunk chunk; + private final int x; + private final int y; + private final int z; + private final NBTTagCompound nbt; + protected int type; + protected MaterialData data; + protected int flag; + + public CraftBlockState(final Block block) { + this.world = (CraftWorld) block.getWorld(); + this.x = block.getX(); + this.y = block.getY(); + this.z = block.getZ(); + this.type = block.getTypeId(); + this.chunk = (CraftChunk) block.getChunk(); + this.flag = 3; + + createData(block.getData()); + TileEntity te = world.getHandle().getTileEntity(new BlockPos(this.x, this.y, this.z)); + if (te != null) + { + nbt = new NBTTagCompound(); + te.writeToNBT(nbt); + } + else nbt = null; + } + + public CraftBlockState(final Block block, int flag) { + this(block); + this.flag = flag; + } + + public CraftBlockState(Material material) { + world = null; + type = material.getId(); + chunk = null; + x = y = z = 0; + this.nbt = null; + } + + public CraftBlockState(BlockSnapshot blocksnapshot) + { + this.world = blocksnapshot.getWorld().getWorld(); + this.x = blocksnapshot.getPos().getX(); + this.y = blocksnapshot.getPos().getY(); + this.z = blocksnapshot.getPos().getZ(); + this.type = net.minecraft.block.Block.getIdFromBlock(blocksnapshot.getReplacedBlock().getBlock()); + this.chunk = (CraftChunk) this.world.getBlockAt(this.x, this.y, this.z).getChunk(); + this.flag = 3; + this.nbt = blocksnapshot.getNbt(); + + this.createData((byte) blocksnapshot.getMeta()); + } + + + + public static CraftBlockState getBlockState(net.minecraft.world.World world, int x, int y, int z) { + return new CraftBlockState(world.getWorld().getBlockAt(x, y, z)); + } + + public static CraftBlockState getBlockState(net.minecraft.world.World world, int x, int y, int z, int flag) { + return new CraftBlockState(world.getWorld().getBlockAt(x, y, z), flag); + } + + public World getWorld() { + requirePlaced(); + return world; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public Chunk getChunk() { + requirePlaced(); + return chunk; + } + + public void setData(final MaterialData data) { + Material mat = getType(); + + if ((mat == null) || (mat.getData() == null)) { + this.data = data; + } else { + if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) { + this.data = data; + } else { + throw new IllegalArgumentException("Provided data is not of type " + + mat.getData().getName() + ", found " + data.getClass().getName()); + } + } + } + + public MaterialData getData() { + return data; + } + + public void setType(final Material type) { + setTypeId(type.getId()); + } + + public boolean setTypeId(final int type) { + if (this.type != type) { + this.type = type; + + createData((byte) 0); + } + return true; + } + + public Material getType() { + return Material.getBlockMaterial(getTypeId()); + } + + public void setFlag(int flag) { + this.flag = flag; + } + + public int getFlag() { + return flag; + } + + public int getTypeId() { + return type; + } + + public byte getLightLevel() { + return getBlock().getLightLevel(); + } + + public Block getBlock() { + requirePlaced(); + return world.getBlockAt(x, y, z); + } + + public boolean update() { + return update(false); + } + + public boolean update(boolean force) { + return update(force, true); + } + + public boolean update(boolean force, boolean applyPhysics) { + if (!isPlaced()) { + return true; + } + Block block = getBlock(); + + if (block.getType() != getType()) { + if (!force) { + return false; + } + } + + BlockPos pos = new BlockPos(x, y, z); + IBlockState newBlock = CraftMagicNumbers.getBlock(getType()).getStateFromMeta(this.getRawData()); + block.setTypeIdAndData(getTypeId(), getRawData(), applyPhysics); + world.getHandle().notifyBlockUpdate( + pos, + CraftMagicNumbers.getBlock(block).getStateFromMeta(block.getData()), + newBlock, + 3 + ); + + // Update levers etc + if (applyPhysics && getData() instanceof Attachable) { + world.getHandle().notifyNeighborsOfStateChange(pos.offset(CraftBlock.blockFaceToNotch(((Attachable) getData()).getAttachedFace())), newBlock.getBlock(), false); + } + + if (nbt != null) + { + TileEntity te = world.getHandle().getTileEntity(new BlockPos(this.x, this.y, this.z)); + if (te != null) + { + te.readFromNBT(nbt); + } + } + return true; + } + + private void createData(final byte data) { + Material mat = getType(); + if (mat == null || mat.getData() == null) { + this.data = new MaterialData(type, data); + } else { + this.data = mat.getNewData(data); + } + } + + public byte getRawData() { + return data.getData(); + } + + public Location getLocation() { + return new Location(world, x, y, z); + } + + public Location getLocation(Location loc) { + if (loc != null) { + loc.setWorld(world); + loc.setX(x); + loc.setY(y); + loc.setZ(z); + loc.setYaw(0); + loc.setPitch(0); + } + + return loc; + } + + public void setRawData(byte data) { + this.data.setData(data); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CraftBlockState other = (CraftBlockState) obj; + if (this.world != other.world && (this.world == null || !this.world.equals(other.world))) { + return false; + } + if (this.x != other.x) { + return false; + } + if (this.y != other.y) { + return false; + } + if (this.z != other.z) { + return false; + } + if (this.type != other.type) { + return false; + } + if (this.data != other.data && (this.data == null || !this.data.equals(other.data))) { + return false; + } + if (this.nbt != other.nbt && (this.nbt == null || !this.nbt.equals(other.nbt))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 73 * hash + (this.world != null ? this.world.hashCode() : 0); + hash = 73 * hash + this.x; + hash = 73 * hash + this.y; + hash = 73 * hash + this.z; + hash = 73 * hash + this.type; + hash = 73 * hash + (this.data != null ? this.data.hashCode() : 0); + hash = 73 * hash + (this.nbt != null ? this.nbt.hashCode() : 0); + return hash; + } + + public TileEntity getTileEntity() { + if (nbt != null) + return TileEntity.create(this.world.getHandle(), nbt); + else return null; + } + + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + requirePlaced(); + chunk.getCraftWorld().getBlockMetadata().setMetadata(getBlock(), metadataKey, newMetadataValue); + } + + public List getMetadata(String metadataKey) { + requirePlaced(); + return chunk.getCraftWorld().getBlockMetadata().getMetadata(getBlock(), metadataKey); + } + + public boolean hasMetadata(String metadataKey) { + requirePlaced(); + return chunk.getCraftWorld().getBlockMetadata().hasMetadata(getBlock(), metadataKey); + } + + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + requirePlaced(); + chunk.getCraftWorld().getBlockMetadata().removeMetadata(getBlock(), metadataKey, owningPlugin); + } + + @Override + public boolean isPlaced() { + return world != null; + } + + protected void requirePlaced() { + if (!isPlaced()) { + throw new IllegalStateException("The blockState must be placed to call this method"); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBrewingStand.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBrewingStand.java new file mode 100644 index 00000000..ba1afbaa --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBrewingStand.java @@ -0,0 +1,73 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityBrewingStand; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BrewingStand; +import org.bukkit.craftbukkit.inventory.CraftInventoryBrewer; +import org.bukkit.inventory.BrewerInventory; + +public class CraftBrewingStand extends CraftContainer implements BrewingStand { + + public CraftBrewingStand(Block block) { + super(block, TileEntityBrewingStand.class); + } + + public CraftBrewingStand(final Material material, final TileEntityBrewingStand te) { + super(material, te); + } + + @Override + public BrewerInventory getSnapshotInventory() { + return new CraftInventoryBrewer(this.getSnapshot()); + } + + @Override + public BrewerInventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventoryBrewer(this.getTileEntity()); + } + + @Override + public int getBrewingTime() { + return this.getSnapshot().getField(0); + } + + @Override + public void setBrewingTime(int brewTime) { + this.getSnapshot().setField(0, brewTime); + } + + @Override + public int getFuelLevel() { + return this.getSnapshot().getField(1); + } + + @Override + public void setFuelLevel(int level) { + this.getSnapshot().setField(1, level); + } + + @Override + public String getCustomName() { + TileEntityBrewingStand brewingStand = this.getSnapshot(); + return brewingStand.hasCustomName() ? brewingStand.getName() : null; + } + + @Override + public void setCustomName(String name) { + this.getSnapshot().setName(name); + } + + @Override + public void applyTo(TileEntityBrewingStand brewingStand) { + super.applyTo(brewingStand); + + if (!this.getSnapshot().hasCustomName()) { + brewingStand.setName(null); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java new file mode 100644 index 00000000..7c746ecf --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java @@ -0,0 +1,77 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest; +import org.bukkit.inventory.Inventory; + +public class CraftChest extends CraftLootable implements Chest { + + public CraftChest(final Block block) { + super(block, TileEntityChest.class); + } + + public CraftChest(final Material material, final TileEntityChest te) { + super(material, te); + } + + @Override + public Inventory getSnapshotInventory() { + return new CraftInventory(this.getSnapshot()); + } + + @Override + public Inventory getBlockInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventory(this.getTileEntity()); + } + + @Override + public Inventory getInventory() { + CraftInventory inventory = (CraftInventory) this.getBlockInventory(); + if (!isPlaced()) { + return inventory; + } + + // The logic here is basically identical to the logic in BlockChest.interact + int x = this.getX(); + int y = this.getY(); + int z = this.getZ(); + CraftWorld world = (CraftWorld) this.getWorld(); + + int id; + if (world.getBlockTypeIdAt(x, y, z) == Material.CHEST.getId()) { + id = Material.CHEST.getId(); + } else if (world.getBlockTypeIdAt(x, y, z) == Material.TRAPPED_CHEST.getId()) { + id = Material.TRAPPED_CHEST.getId(); + } else { + throw new IllegalStateException("CraftChest is not a chest but is instead " + world.getBlockAt(x, y, z)); + } + + if (world.getBlockTypeIdAt(x - 1, y, z) == id) { + CraftInventory left = new CraftInventory((TileEntityChest) world.getHandle().getTileEntity(new BlockPos(x - 1, y, z))); + inventory = new CraftInventoryDoubleChest(left, inventory); + } + if (world.getBlockTypeIdAt(x + 1, y, z) == id) { + CraftInventory right = new CraftInventory((TileEntityChest) world.getHandle().getTileEntity(new BlockPos(x + 1, y, z))); + inventory = new CraftInventoryDoubleChest(inventory, right); + } + if (world.getBlockTypeIdAt(x, y, z - 1) == id) { + CraftInventory left = new CraftInventory((TileEntityChest) world.getHandle().getTileEntity(new BlockPos(x, y, z - 1))); + inventory = new CraftInventoryDoubleChest(left, inventory); + } + if (world.getBlockTypeIdAt(x, y, z + 1) == id) { + CraftInventory right = new CraftInventory((TileEntityChest) world.getHandle().getTileEntity(new BlockPos(x, y, z + 1))); + inventory = new CraftInventoryDoubleChest(inventory, right); + } + return inventory; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java new file mode 100644 index 00000000..0aef832a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java @@ -0,0 +1,56 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityCommandBlock; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.CommandBlock; + +public class CraftCommandBlock extends CraftBlockEntityState implements CommandBlock { + + private String command; + private String name; + + public CraftCommandBlock(Block block) { + super(block, TileEntityCommandBlock.class); + } + + public CraftCommandBlock(final Material material, final TileEntityCommandBlock te) { + super(material, te); + } + + @Override + public void load(TileEntityCommandBlock commandBlock) { + super.load(commandBlock); + + command = commandBlock.getCommandBlockLogic().getCommand(); + name = commandBlock.getCommandBlockLogic().getName(); + } + + @Override + public String getCommand() { + return command; + } + + @Override + public void setCommand(String command) { + this.command = command != null ? command : ""; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name != null ? name : "@"; + } + + @Override + public void applyTo(TileEntityCommandBlock commandBlock) { + super.applyTo(commandBlock); + + commandBlock.getCommandBlockLogic().setCommand(command); + commandBlock.getCommandBlockLogic().setName(name); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftComparator.java b/src/main/java/org/bukkit/craftbukkit/block/CraftComparator.java new file mode 100644 index 00000000..0ef5cc6d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftComparator.java @@ -0,0 +1,17 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityComparator; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Comparator; + +public class CraftComparator extends CraftBlockEntityState implements Comparator { + + public CraftComparator(final Block block) { + super(block, TileEntityComparator.class); + } + + public CraftComparator(final Material material, final TileEntityComparator te) { + super(material, te); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java new file mode 100644 index 00000000..184b3fd4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java @@ -0,0 +1,33 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityLockable; +import net.minecraft.world.LockCode; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Container; + +public abstract class CraftContainer extends CraftBlockEntityState implements Container { + + public CraftContainer(Block block, Class tileEntityClass) { + super(block, tileEntityClass); + } + + public CraftContainer(final Material material, T tileEntity) { + super(material, tileEntity); + } + + @Override + public boolean isLocked() { + return this.getSnapshot().isLocked(); + } + + @Override + public String getLock() { + return this.getSnapshot().getLockCode().getLock(); + } + + @Override + public void setLock(String key) { + this.getSnapshot().setLockCode(key == null ? LockCode.EMPTY_CODE : new LockCode(key)); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java new file mode 100644 index 00000000..54dba247 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java @@ -0,0 +1,124 @@ +package org.bukkit.craftbukkit.block; + +import com.google.common.base.Preconditions; +import net.minecraft.tileentity.TileEntityMobSpawner; +import net.minecraft.util.ResourceLocation; +import org.bukkit.Material; + +import org.bukkit.block.Block; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.EntityType; + +public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner { + + public CraftCreatureSpawner(final Block block) { + super(block, TileEntityMobSpawner.class); + } + + public CraftCreatureSpawner(final Material material, TileEntityMobSpawner te) { + super(material, te); + } + + @Override + public EntityType getSpawnedType() { + ResourceLocation key = this.getSnapshot().getSpawnerBaseLogic().getEntityId(); + return (key == null) ? EntityType.PIG : EntityType.fromName(key.getResourcePath()); + } + + @Override + public void setSpawnedType(EntityType entityType) { + if (entityType == null || entityType.getName() == null) { + throw new IllegalArgumentException("Can't spawn EntityType " + entityType + " from mobspawners!"); + } + + this.getSnapshot().getSpawnerBaseLogic().setEntityId(new ResourceLocation(entityType.getName())); + } + + @Override + public String getCreatureTypeName() { + return this.getSnapshot().getSpawnerBaseLogic().getEntityId().getResourcePath(); + } + + @Override + public void setCreatureTypeByName(String creatureType) { + // Verify input + EntityType type = EntityType.fromName(creatureType); + if (type == null) { + return; + } + setSpawnedType(type); + } + + @Override + public int getDelay() { + return this.getSnapshot().getSpawnerBaseLogic().spawnDelay; + } + + @Override + public void setDelay(int delay) { + this.getSnapshot().getSpawnerBaseLogic().spawnDelay = delay; + } + + @Override + public int getMinSpawnDelay() { + return this.getSnapshot().getSpawnerBaseLogic().minSpawnDelay; + } + + @Override + public void setMinSpawnDelay(int spawnDelay) { + Preconditions.checkArgument(spawnDelay <= getMaxSpawnDelay(), "Minimum Spawn Delay must be less than or equal to Maximum Spawn Delay"); + this.getSnapshot().getSpawnerBaseLogic().minSpawnDelay = spawnDelay; + } + + @Override + public int getMaxSpawnDelay() { + return this.getSnapshot().getSpawnerBaseLogic().maxSpawnDelay; + } + + @Override + public void setMaxSpawnDelay(int spawnDelay) { + Preconditions.checkArgument(spawnDelay > 0, "Maximum Spawn Delay must be greater than 0."); + Preconditions.checkArgument(spawnDelay >= getMinSpawnDelay(), "Maximum Spawn Delay must be greater than or equal to Minimum Spawn Delay"); + this.getSnapshot().getSpawnerBaseLogic().maxSpawnDelay = spawnDelay; + } + + @Override + public int getMaxNearbyEntities() { + return this.getSnapshot().getSpawnerBaseLogic().maxNearbyEntities; + } + + @Override + public void setMaxNearbyEntities(int maxNearbyEntities) { + this.getSnapshot().getSpawnerBaseLogic().maxNearbyEntities = maxNearbyEntities; + } + + @Override + public int getSpawnCount() { + return this.getSnapshot().getSpawnerBaseLogic().spawnCount; + } + + @Override + public void setSpawnCount(int count) { + this.getSnapshot().getSpawnerBaseLogic().spawnCount = count; + } + + @Override + public int getRequiredPlayerRange() { + return this.getSnapshot().getSpawnerBaseLogic().activatingRangeFromPlayer; + } + + @Override + public void setRequiredPlayerRange(int requiredPlayerRange) { + this.getSnapshot().getSpawnerBaseLogic().activatingRangeFromPlayer = requiredPlayerRange; + } + + @Override + public int getSpawnRange() { + return this.getSnapshot().getSpawnerBaseLogic().spawnRange; + } + + @Override + public void setSpawnRange(int spawnRange) { + this.getSnapshot().getSpawnerBaseLogic().spawnRange = spawnRange; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDaylightDetector.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDaylightDetector.java new file mode 100644 index 00000000..8be5685d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDaylightDetector.java @@ -0,0 +1,17 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityDaylightDetector; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.DaylightDetector; + +public class CraftDaylightDetector extends CraftBlockEntityState implements DaylightDetector { + + public CraftDaylightDetector(final Block block) { + super(block, TileEntityDaylightDetector.class); + } + + public CraftDaylightDetector(final Material material, final TileEntityDaylightDetector te) { + super(material, te); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java new file mode 100644 index 00000000..de76d58a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java @@ -0,0 +1,65 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.block.BlockDispenser; +import net.minecraft.init.Blocks; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Dispenser; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource; +import org.bukkit.inventory.Inventory; +import org.bukkit.projectiles.BlockProjectileSource; + +public class CraftDispenser extends CraftLootable implements Dispenser { + + public CraftDispenser(final Block block) { + super(block, TileEntityDispenser.class); + } + + public CraftDispenser(final Material material, final TileEntityDispenser te) { + super(material, te); + } + + @Override + public Inventory getSnapshotInventory() { + return new CraftInventory(this.getSnapshot()); + } + + @Override + public Inventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventory(this.getTileEntity()); + } + + @Override + public BlockProjectileSource getBlockProjectileSource() { + Block block = getBlock(); + + if (block.getType() != Material.DISPENSER) { + return null; + } + + return new CraftBlockProjectileSource((TileEntityDispenser) this.getTileEntityFromWorld()); + } + + @Override + public boolean dispense() { + Block block = getBlock(); + + if (block.getType() == Material.DISPENSER) { + CraftWorld world = (CraftWorld) this.getWorld(); + BlockDispenser dispense = (BlockDispenser) Blocks.DISPENSER; + + dispense.dispense(world.getHandle(), new BlockPos(getX(), getY(), getZ())); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDropper.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDropper.java new file mode 100644 index 00000000..efa6ce7b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDropper.java @@ -0,0 +1,49 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.block.BlockDropper; +import net.minecraft.init.Blocks; +import net.minecraft.tileentity.TileEntityDropper; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Dropper; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.inventory.Inventory; + +public class CraftDropper extends CraftLootable implements Dropper { + + public CraftDropper(final Block block) { + super(block, TileEntityDropper.class); + } + + public CraftDropper(final Material material, TileEntityDropper te) { + super(material, te); + } + + @Override + public Inventory getSnapshotInventory() { + return new CraftInventory(this.getSnapshot()); + } + + @Override + public Inventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventory(this.getTileEntity()); + } + + @Override + public void drop() { + Block block = getBlock(); + + if (block.getType() == Material.DROPPER) { + CraftWorld world = (CraftWorld) this.getWorld(); + BlockDropper drop = (BlockDropper) Blocks.DROPPER; + + drop.dispense(world.getHandle(), new BlockPos(getX(), getY(), getZ())); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java new file mode 100644 index 00000000..da8eef1f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityEnchantmentTable; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.EnchantingTable; + +public class CraftEnchantingTable extends CraftBlockEntityState implements EnchantingTable { + + public CraftEnchantingTable(final Block block) { + super(block, TileEntityEnchantmentTable.class); + } + + public CraftEnchantingTable(final Material material, final TileEntityEnchantmentTable te) { + super(material, te); + } + + @Override + public String getCustomName() { + TileEntityEnchantmentTable enchant = this.getSnapshot(); + return enchant.hasCustomName() ? enchant.getName() : null; + } + + @Override + public void setCustomName(String name) { + this.getSnapshot().setCustomName(name); + } + + @Override + public void applyTo(TileEntityEnchantmentTable enchantingTable) { + super.applyTo(enchantingTable); + + if (!this.getSnapshot().hasCustomName()) { + enchantingTable.setCustomName(null); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEndGateway.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEndGateway.java new file mode 100644 index 00000000..cd75b55c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEndGateway.java @@ -0,0 +1,56 @@ +package org.bukkit.craftbukkit.block; + +import java.util.Objects; +import net.minecraft.tileentity.TileEntityEndGateway; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.EndGateway; + +public class CraftEndGateway extends CraftBlockEntityState implements EndGateway { + + public CraftEndGateway(Block block) { + super(block, TileEntityEndGateway.class); + } + + public CraftEndGateway(final Material material, TileEntityEndGateway te) { + super(material, te); + } + + @Override + public Location getExitLocation() { + BlockPos pos = this.getSnapshot().exitPortal; + return pos == null ? null : new Location(this.isPlaced() ? this.getWorld() : null, pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public void setExitLocation(Location location) { + if (location == null) { + this.getSnapshot().exitPortal = null; + } else if (!Objects.equals(location.getWorld(), this.isPlaced() ? this.getWorld() : null)) { + throw new IllegalArgumentException("Cannot set exit location to different world"); + } else { + this.getSnapshot().exitPortal = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + } + + @Override + public boolean isExactTeleport() { + return this.getSnapshot().exactTeleport; + } + + @Override + public void setExactTeleport(boolean exact) { + this.getSnapshot().exactTeleport = exact; + } + + @Override + public void applyTo(TileEntityEndGateway endGateway) { + super.applyTo(endGateway); + + if (this.getSnapshot().exitPortal == null) { + endGateway.exitPortal = null; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java new file mode 100644 index 00000000..9a44b680 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java @@ -0,0 +1,17 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityEnderChest; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.EnderChest; + +public class CraftEnderChest extends CraftBlockEntityState implements EnderChest { + + public CraftEnderChest(final Block block) { + super(block, TileEntityEnderChest.class); + } + + public CraftEnderChest(final Material material, final TileEntityEnderChest te) { + super(material, te); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFlowerPot.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFlowerPot.java new file mode 100644 index 00000000..aae87c61 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFlowerPot.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntityFlowerPot; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.FlowerPot; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.material.MaterialData; + +public class CraftFlowerPot extends CraftBlockEntityState implements FlowerPot { + + private MaterialData contents; + + public CraftFlowerPot(Block block) { + super(block, TileEntityFlowerPot.class); + } + + public CraftFlowerPot(Material material, TileEntityFlowerPot te) { + super(material, te); + } + + @Override + public void load(TileEntityFlowerPot pot) { + super.load(pot); + + contents = (pot.getFlowerPotItem() == null) ? null : CraftItemStack.asBukkitCopy(pot.getFlowerItemStack()).getData(); + } + + @Override + public MaterialData getContents() { + return contents; + } + + @Override + public void setContents(MaterialData item) { + contents = item; + } + + @Override + public void applyTo(TileEntityFlowerPot pot) { + super.applyTo(pot); + + pot.setItemStack(contents == null ? ItemStack.EMPTY : CraftItemStack.asNMSCopy(contents.toItemStack(1))); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java new file mode 100644 index 00000000..c39383fb --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java @@ -0,0 +1,73 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityFurnace; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Furnace; +import org.bukkit.craftbukkit.inventory.CraftInventoryFurnace; +import org.bukkit.inventory.FurnaceInventory; + +public class CraftFurnace extends CraftContainer implements Furnace { + + public CraftFurnace(final Block block) { + super(block, TileEntityFurnace.class); + } + + public CraftFurnace(final Material material, final TileEntityFurnace te) { + super(material, te); + } + + @Override + public FurnaceInventory getSnapshotInventory() { + return new CraftInventoryFurnace(this.getSnapshot()); + } + + @Override + public FurnaceInventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventoryFurnace(this.getTileEntity()); + } + + @Override + public short getBurnTime() { + return (short) this.getSnapshot().getField(0); + } + + @Override + public void setBurnTime(short burnTime) { + this.getSnapshot().setField(0, burnTime); + } + + @Override + public short getCookTime() { + return (short) this.getSnapshot().getField(2); + } + + @Override + public void setCookTime(short cookTime) { + this.getSnapshot().setField(2, cookTime); + } + + @Override + public String getCustomName() { + TileEntityFurnace furnace = this.getSnapshot(); + return furnace.hasCustomName() ? furnace.getName() : null; + } + + @Override + public void setCustomName(String name) { + this.getSnapshot().setCustomInventoryName(name); + } + + @Override + public void applyTo(TileEntityFurnace furnace) { + super.applyTo(furnace); + + if (!this.getSnapshot().hasCustomName()) { + furnace.setCustomInventoryName(null); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java b/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java new file mode 100644 index 00000000..5c3ee84d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java @@ -0,0 +1,33 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityHopper; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Hopper; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.inventory.Inventory; + +public class CraftHopper extends CraftLootable implements Hopper { + + public CraftHopper(final Block block) { + super(block, TileEntityHopper.class); + } + + public CraftHopper(final Material material, final TileEntityHopper te) { + super(material, te); + } + + @Override + public Inventory getSnapshotInventory() { + return new CraftInventory(this.getSnapshot()); + } + + @Override + public Inventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventory(this.getTileEntity()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftJukebox.java b/src/main/java/org/bukkit/craftbukkit/block/CraftJukebox.java new file mode 100644 index 00000000..c7a89e83 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftJukebox.java @@ -0,0 +1,88 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.block.BlockJukebox; +import net.minecraft.block.BlockJukebox.TileEntityJukebox; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Effect; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Jukebox; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +public class CraftJukebox extends CraftBlockEntityState implements Jukebox { + + public CraftJukebox(final Block block) { + super(block, TileEntityJukebox.class); + } + + public CraftJukebox(final Material material, TileEntityJukebox te) { + super(material, te); + } + + @Override + public boolean update(boolean force, boolean applyPhysics) { + boolean result = super.update(force, applyPhysics); + + if (result && this.isPlaced() && this.getType() == Material.JUKEBOX) { + CraftWorld world = (CraftWorld) this.getWorld(); + Material record = this.getPlaying(); + if (record == Material.AIR) { + world.getHandle().setBlockState(new BlockPos(this.getX(), this.getY(), this.getZ()), + Blocks.JUKEBOX.getDefaultState() + .withProperty(BlockJukebox.HAS_RECORD, false), 3); + } else { + world.getHandle().setBlockState(new BlockPos(this.getX(), this.getY(), this.getZ()), + Blocks.JUKEBOX.getDefaultState() + .withProperty(BlockJukebox.HAS_RECORD, true), 3); + } + world.playEffect(this.getLocation(), Effect.RECORD_PLAY, record.getId()); + } + + return result; + } + + @Override + public Material getPlaying() { + ItemStack record = this.getSnapshot().getRecord(); + if (record.isEmpty()) { + return Material.AIR; + } + return CraftMagicNumbers.getMaterial(record.getItem()); + } + + @Override + public void setPlaying(Material record) { + if (record == null || CraftMagicNumbers.getItem(record) == null) { + record = Material.AIR; + } + + this.getSnapshot().setRecord(new ItemStack(CraftMagicNumbers.getItem(record), 1)); + if (record == Material.AIR) { + setRawData((byte) 0); + } else { + setRawData((byte) 1); + } + } + + @Override + public boolean isPlaying() { + return getRawData() == 1; + } + + @Override + public boolean eject() { + requirePlaced(); + TileEntity tileEntity = this.getTileEntityFromWorld(); + if (!(tileEntity instanceof TileEntityJukebox)) return false; + + TileEntityJukebox jukebox = (TileEntityJukebox) tileEntity; + boolean result = !jukebox.getRecord().isEmpty(); + CraftWorld world = (CraftWorld) this.getWorld(); + ((BlockJukebox) Blocks.JUKEBOX).dropRecord(world.getHandle(), new BlockPos(getX(), getY(), getZ()), null); + return result; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java new file mode 100644 index 00000000..28f3d811 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityLockableLoot; +import org.bukkit.Material; +import org.bukkit.Nameable; +import org.bukkit.block.Block; + +public abstract class CraftLootable extends CraftContainer implements Nameable { + + public CraftLootable(Block block, Class tileEntityClass) { + super(block, tileEntityClass); + } + + public CraftLootable(Material material, T tileEntity) { + super(material, tileEntity); + } + + @Override + public String getCustomName() { + T lootable = this.getSnapshot(); + return lootable.hasCustomName() ? lootable.getName() : null; + } + + @Override + public void setCustomName(String name) { + this.getSnapshot().setCustomName(name); + } + + @Override + public void applyTo(T lootable) { + super.applyTo(lootable); + + if (!this.getSnapshot().hasCustomName()) { + lootable.setCustomName(null); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java new file mode 100644 index 00000000..4018774e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java @@ -0,0 +1,82 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntityNote; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Instrument; +import org.bukkit.Material; +import org.bukkit.Note; +import org.bukkit.block.Block; +import org.bukkit.block.NoteBlock; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +public class CraftNoteBlock extends CraftBlockEntityState implements NoteBlock { + + public CraftNoteBlock(final Block block) { + super(block, TileEntityNote.class); + } + + public CraftNoteBlock(final Material material, final TileEntityNote te) { + super(material, te); + } + + @Override + public Note getNote() { + return new Note(this.getSnapshot().note); + } + + @Override + public byte getRawNote() { + return this.getSnapshot().note; + } + + @Override + public void setNote(Note note) { + this.getSnapshot().note = note.getId(); + } + + @Override + public void setRawNote(byte note) { + this.getSnapshot().note = note; + } + + @Override + public boolean play() { + Block block = getBlock(); + + if (block.getType() == Material.NOTE_BLOCK) { + TileEntityNote note = (TileEntityNote) this.getTileEntityFromWorld(); + CraftWorld world = (CraftWorld) this.getWorld(); + note.triggerNote(world.getHandle(), new BlockPos(getX(), getY(), getZ())); + return true; + } else { + return false; + } + } + + @Override + public boolean play(byte instrument, byte note) { + Block block = getBlock(); + + if (block.getType() == Material.NOTE_BLOCK) { + CraftWorld world = (CraftWorld) this.getWorld(); + world.getHandle().addBlockEvent(new BlockPos(getX(), getY(), getZ()), CraftMagicNumbers.getBlock(block), instrument, note); + return true; + } else { + return false; + } + } + + @Override + public boolean play(Instrument instrument, Note note) { + Block block = getBlock(); + + if (block.getType() == Material.NOTE_BLOCK) { + CraftWorld world = (CraftWorld) this.getWorld(); + world.getHandle().addBlockEvent(new BlockPos(getX(), getY(), getZ()), CraftMagicNumbers.getBlock(block), instrument.getType(), note.getId()); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java b/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java new file mode 100644 index 00000000..0b9aac43 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java @@ -0,0 +1,43 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.block.BlockShulkerBox; +import net.minecraft.tileentity.TileEntityShulkerBox; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.ShulkerBox; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.Inventory; + +public class CraftShulkerBox extends CraftLootable implements ShulkerBox { + + public CraftShulkerBox(final Block block) { + super(block, TileEntityShulkerBox.class); + } + + public CraftShulkerBox(final Material material, final TileEntityShulkerBox te) { + super(material, te); + } + + @Override + public Inventory getSnapshotInventory() { + return new CraftInventory(this.getSnapshot()); + } + + @Override + public Inventory getInventory() { + if (!this.isPlaced()) { + return this.getSnapshotInventory(); + } + + return new CraftInventory(this.getTileEntity()); + } + + @Override + public DyeColor getColor() { + net.minecraft.block.Block block = CraftMagicNumbers.getBlock(this.getType()); + + return DyeColor.getByWoolData((byte) ((BlockShulkerBox) block).color.getMetadata()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java new file mode 100644 index 00000000..bfe41f98 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java @@ -0,0 +1,79 @@ +package org.bukkit.craftbukkit.block; + +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class CraftSign extends CraftBlockEntityState implements Sign { + + private String[] lines; + + public CraftSign(final Block block) { + super(block, TileEntitySign.class); + } + + public CraftSign(final Material material, final TileEntitySign te) { + super(material, te); + } + + @Override + public void load(TileEntitySign sign) { + super.load(sign); + + lines = new String[sign.signText.length]; + System.arraycopy(revertComponents(sign.signText), 0, lines, 0, lines.length); + } + + @Override + public String[] getLines() { + return lines; + } + + @Override + public String getLine(int index) throws IndexOutOfBoundsException { + return lines[index]; + } + + @Override + public void setLine(int index, String line) throws IndexOutOfBoundsException { + lines[index] = line; + } + + @Override + public void applyTo(TileEntitySign sign) { + super.applyTo(sign); + + ITextComponent[] newLines = sanitizeLines(lines); + System.arraycopy(newLines, 0, sign.signText, 0, 4); + } + + public static ITextComponent[] sanitizeLines(String[] lines) { + ITextComponent[] components = new ITextComponent[4]; + + for (int i = 0; i < 4; i++) { + if (i < lines.length && lines[i] != null) { + components[i] = CraftChatMessage.fromString(lines[i])[0]; + } else { + components[i] = new TextComponentString(""); + } + } + + return components; + } + + public static String[] revertComponents(ITextComponent[] components) { + String[] lines = new String[components.length]; + for (int i = 0; i < lines.length; i++) { + lines[i] = revertComponent(components[i]); + } + return lines; + } + + private static String revertComponent(ITextComponent component) { + return CraftChatMessage.fromComponent(component); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java new file mode 100644 index 00000000..6b238e1e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java @@ -0,0 +1,245 @@ +package org.bukkit.craftbukkit.block; + +import com.google.common.base.Preconditions; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntitySkull; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; + +import org.bukkit.SkullType; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Skull; + +public class CraftSkull extends CraftBlockEntityState implements Skull { + + private static final int MAX_OWNER_LENGTH = 16; + private GameProfile profile; + private SkullType skullType; + private byte rotation; + + public CraftSkull(final Block block) { + super(block, TileEntitySkull.class); + } + + public CraftSkull(final Material material, final TileEntitySkull te) { + super(material, te); + } + + @Override + public void load(TileEntitySkull skull) { + super.load(skull); + + profile = skull.getPlayerProfile(); + skullType = getSkullType(skull.getSkullType()); + rotation = (byte) skull.skullRotation; + } + + static SkullType getSkullType(int id) { + switch (id) { + default: + case 0: + return SkullType.SKELETON; + case 1: + return SkullType.WITHER; + case 2: + return SkullType.ZOMBIE; + case 3: + return SkullType.PLAYER; + case 4: + return SkullType.CREEPER; + case 5: + return SkullType.DRAGON; + } + } + + static int getSkullType(SkullType type) { + switch(type) { + default: + case SKELETON: + return 0; + case WITHER: + return 1; + case ZOMBIE: + return 2; + case PLAYER: + return 3; + case CREEPER: + return 4; + case DRAGON: + return 5; + } + } + + static byte getBlockFace(BlockFace rotation) { + switch (rotation) { + case NORTH: + return 0; + case NORTH_NORTH_EAST: + return 1; + case NORTH_EAST: + return 2; + case EAST_NORTH_EAST: + return 3; + case EAST: + return 4; + case EAST_SOUTH_EAST: + return 5; + case SOUTH_EAST: + return 6; + case SOUTH_SOUTH_EAST: + return 7; + case SOUTH: + return 8; + case SOUTH_SOUTH_WEST: + return 9; + case SOUTH_WEST: + return 10; + case WEST_SOUTH_WEST: + return 11; + case WEST: + return 12; + case WEST_NORTH_WEST: + return 13; + case NORTH_WEST: + return 14; + case NORTH_NORTH_WEST: + return 15; + default: + throw new IllegalArgumentException("Invalid BlockFace rotation: " + rotation); + } + } + + static BlockFace getBlockFace(byte rotation) { + switch (rotation) { + case 0: + return BlockFace.NORTH; + case 1: + return BlockFace.NORTH_NORTH_EAST; + case 2: + return BlockFace.NORTH_EAST; + case 3: + return BlockFace.EAST_NORTH_EAST; + case 4: + return BlockFace.EAST; + case 5: + return BlockFace.EAST_SOUTH_EAST; + case 6: + return BlockFace.SOUTH_EAST; + case 7: + return BlockFace.SOUTH_SOUTH_EAST; + case 8: + return BlockFace.SOUTH; + case 9: + return BlockFace.SOUTH_SOUTH_WEST; + case 10: + return BlockFace.SOUTH_WEST; + case 11: + return BlockFace.WEST_SOUTH_WEST; + case 12: + return BlockFace.WEST; + case 13: + return BlockFace.WEST_NORTH_WEST; + case 14: + return BlockFace.NORTH_WEST; + case 15: + return BlockFace.NORTH_NORTH_WEST; + default: + throw new AssertionError(rotation); + } + } + + @Override + public boolean hasOwner() { + return profile != null; + } + + @Override + public String getOwner() { + return hasOwner() ? profile.getName() : null; + } + + @Override + public boolean setOwner(String name) { + if (name == null || name.length() > MAX_OWNER_LENGTH) { + return false; + } + + GameProfile profile = MinecraftServer.getServerCB().getPlayerProfileCache().getGameProfileForUsername(name); + if (profile == null) { + return false; + } + + if (skullType != SkullType.PLAYER) { + skullType = SkullType.PLAYER; + } + + this.profile = profile; + return true; + } + + @Override + public OfflinePlayer getOwningPlayer() { + if (profile != null) { + if (profile.getId() != null) { + return Bukkit.getOfflinePlayer(profile.getId()); + } + + if (profile.getName() != null) { + return Bukkit.getOfflinePlayer(profile.getName()); + } + } + + return null; + } + + @Override + public void setOwningPlayer(OfflinePlayer player) { + Preconditions.checkNotNull(player, "player"); + + if (skullType != SkullType.PLAYER) { + skullType = SkullType.PLAYER; + } + + this.profile = new GameProfile(player.getUniqueId(), player.getName()); + } + + @Override + public BlockFace getRotation() { + return getBlockFace(rotation); + } + + @Override + public void setRotation(BlockFace rotation) { + this.rotation = getBlockFace(rotation); + } + + @Override + public SkullType getSkullType() { + return skullType; + } + + @Override + public void setSkullType(SkullType skullType) { + this.skullType = skullType; + + if (skullType != SkullType.PLAYER) { + profile = null; + } + } + + @Override + public void applyTo(TileEntitySkull skull) { + super.applyTo(skull); + + if (skullType == SkullType.PLAYER) { + skull.setPlayerProfile(profile); + } else { + skull.setType(getSkullType(skullType)); + } + + skull.setSkullRotation(rotation); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftStructureBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftStructureBlock.java new file mode 100644 index 00000000..274912b6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftStructureBlock.java @@ -0,0 +1,185 @@ +package org.bukkit.craftbukkit.block; + +import com.google.common.base.Preconditions; +import net.minecraft.tileentity.TileEntityStructure; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.BlockPos; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Structure; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.block.structure.UsageMode; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.BlockVector; + +public class CraftStructureBlock extends CraftBlockEntityState implements Structure { + + private static final int MAX_SIZE = 32; + + public CraftStructureBlock(Block block) { + super(block, TileEntityStructure.class); + } + + public CraftStructureBlock(Material material, TileEntityStructure structure) { + super(material, structure); + } + + @Override + public String getStructureName() { + return getSnapshot().getName(); // PAIL: rename getStructureName + } + + @Override + public void setStructureName(String name) { + Preconditions.checkArgument(name != null, "Structure Name cannot be null"); + getSnapshot().setName(name); // PAIL: rename setStructureName + } + + @Override + public String getAuthor() { + return getSnapshot().author; + } + + @Override + public void setAuthor(String author) { + Preconditions.checkArgument(author != null && !author.isEmpty(), "Author name cannot be null nor empty"); + getSnapshot().author = author; // PAIL: rename author + } + + @Override + public void setAuthor(LivingEntity entity) { + Preconditions.checkArgument(entity != null, "Structure Block author entity cannot be null"); + getSnapshot().createdBy(((CraftLivingEntity) entity).getHandle()); // PAIL: rename setAuthor + } + + @Override + public BlockVector getRelativePosition() { + return new BlockVector(getSnapshot().position.getX(), getSnapshot().position.getY(), getSnapshot().position.getZ()); // PAIL: rename relativePosition + } + + @Override + public void setRelativePosition(BlockVector vector) { + Validate.isTrue(isBetween(vector.getBlockX(), -MAX_SIZE, MAX_SIZE), "Structure Size (X) must be between -" + MAX_SIZE + " and " + MAX_SIZE); + Validate.isTrue(isBetween(vector.getBlockY(), -MAX_SIZE, MAX_SIZE), "Structure Size (Y) must be between -" + MAX_SIZE + " and " + MAX_SIZE); + Validate.isTrue(isBetween(vector.getBlockZ(), -MAX_SIZE, MAX_SIZE), "Structure Size (Z) must be between -" + MAX_SIZE + " and " + MAX_SIZE); + getSnapshot().position = new BlockPos(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); // PAIL: rename relativePosition + } + + @Override + public BlockVector getStructureSize() { + return new BlockVector(getSnapshot().size.getX(), getSnapshot().size.getY(), getSnapshot().size.getZ()); // PAIL: rename size + } + + @Override + public void setStructureSize(BlockVector vector) { + Validate.isTrue(isBetween(vector.getBlockX(), 0, MAX_SIZE), "Structure Size (X) must be between 0 and " + MAX_SIZE); + Validate.isTrue(isBetween(vector.getBlockY(), 0, MAX_SIZE), "Structure Size (Y) must be between 0 and " + MAX_SIZE); + Validate.isTrue(isBetween(vector.getBlockZ(), 0, MAX_SIZE), "Structure Size (Z) must be between 0 and " + MAX_SIZE); + getSnapshot().size = new BlockPos(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); // PAIL: rename size + } + + @Override + public void setMirror(Mirror mirror) { + getSnapshot().mirror = net.minecraft.util.Mirror.valueOf(mirror.name()); // PAIL: rename mirror + } + + @Override + public Mirror getMirror() { + return Mirror.valueOf(getSnapshot().mirror.name()); // PAIL: rename mirror + } + + @Override + public void setRotation(StructureRotation rotation) { + getSnapshot().rotation = Rotation.valueOf(rotation.name()); // PAIL: rename rotation + } + + @Override + public StructureRotation getRotation() { + return StructureRotation.valueOf(getSnapshot().rotation.name()); // PAIL: rename rotation + } + + @Override + public void setUsageMode(UsageMode mode) { + getSnapshot().setMode(TileEntityStructure.Mode.valueOf(mode.name())); // PAIL: rename setUsageMode + } + + @Override + public UsageMode getUsageMode() { + return UsageMode.valueOf(getSnapshot().getMode().name()); // PAIL rename getUsageMode + } + + @Override + public void setIgnoreEntities(boolean flag) { + getSnapshot().ignoreEntities = flag; // PAIL: rename ignoreEntities + } + + @Override + public boolean isIgnoreEntities() { + return getSnapshot().ignoreEntities; // PAIL: rename ignoreEntities + } + + @Override + public void setShowAir(boolean showAir) { + getSnapshot().showAir = showAir; // PAIL rename showAir + } + + @Override + public boolean isShowAir() { + return getSnapshot().showAir; // PAIL: rename showAir + } + + @Override + public void setBoundingBoxVisible(boolean showBoundingBox) { + getSnapshot().showBoundingBox = showBoundingBox; // PAIL: rename boundingBoxVisible + } + + @Override + public boolean isBoundingBoxVisible() { + return getSnapshot().showBoundingBox; // PAIL: rename boundingBoxVisible + } + + @Override + public void setIntegrity(float integrity) { + Validate.isTrue(isBetween(integrity, 0.0f, 1.0f), "Integrity must be between 0.0f and 1.0f"); + getSnapshot().integrity = integrity; // PAIL: rename integrity + } + + @Override + public float getIntegrity() { + return getSnapshot().integrity; // PAIL: rename integrity + } + + @Override + public void setSeed(long seed) { + getSnapshot().seed = seed; // PAIL: rename seed + } + + @Override + public long getSeed() { + return getSnapshot().seed; // PAIL: rename seed + } + + @Override + public void setMetadata(String metadata) { + Validate.notNull(metadata, "Structure metadata cannot be null"); + if (getUsageMode() == UsageMode.DATA) { + getSnapshot().metadata = metadata; // PAIL: rename metadata + } + } + + @Override + public String getMetadata() { + return getSnapshot().metadata; // PAIL: rename metadata + } + + private static boolean isBetween(int num, int min, int max) { + return num >= min && num <= max; + } + + private static boolean isBetween(float num, float min, float max) { + return num >= min && num <= max; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/boss/CraftBossBar.java b/src/main/java/org/bukkit/craftbukkit/boss/CraftBossBar.java new file mode 100644 index 00000000..8cbab6c9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/boss/CraftBossBar.java @@ -0,0 +1,177 @@ +package org.bukkit.craftbukkit.boss; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.SPacketUpdateBossInfo; +import net.minecraft.world.BossInfo; +import net.minecraft.world.BossInfoServer; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.entity.Player; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +public class CraftBossBar implements BossBar { + + private final BossInfoServer handle; + private final Set flags; + private BarColor color; + private BarStyle style; + + public CraftBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { + this.flags = flags.length > 0 ? EnumSet.of(flags[0], flags) : EnumSet.noneOf(BarFlag.class); + this.color = color; + this.style = style; + + handle = new BossInfoServer( + CraftChatMessage.fromString(title, true)[0], + convertColor(color), + convertStyle(style) + ); + + updateFlags(); + } + + private BossInfo.Color convertColor(BarColor color) { + BossInfo.Color nmsColor = BossInfo.Color.valueOf(color.name()); + return (nmsColor == null) ? BossInfo.Color.WHITE : nmsColor; + } + + private BossInfo.Overlay convertStyle(BarStyle style) { + switch (style) { + default: + case SOLID: + return BossInfo.Overlay.PROGRESS; + case SEGMENTED_6: + return BossInfo.Overlay.NOTCHED_6; + case SEGMENTED_10: + return BossInfo.Overlay.NOTCHED_10; + case SEGMENTED_12: + return BossInfo.Overlay.NOTCHED_12; + case SEGMENTED_20: + return BossInfo.Overlay.NOTCHED_20; + } + } + + private void updateFlags() { + handle.setDarkenSky(hasFlag(BarFlag.DARKEN_SKY)); + handle.setPlayEndBossMusic(hasFlag(BarFlag.PLAY_BOSS_MUSIC)); + handle.setCreateFog(hasFlag(BarFlag.CREATE_FOG)); + } + + @Override + public String getTitle() { + return CraftChatMessage.fromComponent(handle.getName()); + } + + @Override + public void setTitle(String title) { + handle.name = CraftChatMessage.fromString(title, true)[0]; + handle.sendUpdate(SPacketUpdateBossInfo.Operation.UPDATE_NAME); + } + + @Override + public BarColor getColor() { + return color; + } + + @Override + public void setColor(BarColor color) { + this.color = color; + handle.color = convertColor(color); + handle.sendUpdate(SPacketUpdateBossInfo.Operation.UPDATE_STYLE); + } + + @Override + public BarStyle getStyle() { + return style; + } + + @Override + public void setStyle(BarStyle style) { + this.style = style; + handle.overlay = convertStyle(style); + handle.sendUpdate(SPacketUpdateBossInfo.Operation.UPDATE_STYLE); + } + + @Override + public void addFlag(BarFlag flag) { + flags.add(flag); + updateFlags(); + } + + @Override + public void removeFlag(BarFlag flag) { + flags.remove(flag); + updateFlags(); + } + + @Override + public boolean hasFlag(BarFlag flag) { + return flags.contains(flag); + } + + @Override + public void setProgress(double progress) { + Preconditions.checkArgument(progress >= 0.0 && progress <= 1.0, "Progress must be between 0.0 and 1.0 (%s)", progress); + handle.setPercent((float) progress); + } + + @Override + public double getProgress() { + return handle.getPercent(); + } + + @Override + public void addPlayer(Player player) { + handle.addPlayer(((CraftPlayer) player).getHandle()); + } + + @Override + public void removePlayer(Player player) { + handle.removePlayer(((CraftPlayer) player).getHandle()); + } + + @Override + public List getPlayers() { + ImmutableList.Builder players = ImmutableList.builder(); + for (EntityPlayerMP p : handle.getPlayers()) { + players.add(p.getBukkitEntity()); + } + return players.build(); + } + + @Override + public void setVisible(boolean visible) { + handle.setVisible(visible); + } + + @Override + public boolean isVisible() { + return handle.visible; + } + + @Override + public void show() { + handle.setVisible(true); + } + + @Override + public void hide() { + handle.setVisible(false); + } + + @Override + public void removeAll() { + for (Player player : getPlayers()) { + removePlayer(player); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java new file mode 100644 index 00000000..3ac5c3e8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.chunkio; + +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.AnvilChunkLoader; +import net.minecraft.world.gen.ChunkProviderServer; +import org.bukkit.craftbukkit.util.AsynchronousExecutor; + +public class ChunkIOExecutor { + static final int BASE_THREADS = 1; + static final int PLAYERS_PER_THREAD = 50; + + private static final AsynchronousExecutor instance = new AsynchronousExecutor(new ChunkIOProvider(), BASE_THREADS); + + public static Chunk syncChunkLoad(World world, AnvilChunkLoader loader, ChunkProviderServer provider, int x, int z) { + return instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider)); + } + + public static void queueChunkLoad(World world, AnvilChunkLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) { + instance.add(new QueuedChunk(x, z, loader, world, provider), runnable); + } + + // Abuses the fact that hashCode and equals for QueuedChunk only use world and coords + public static void dropQueuedChunkLoad(World world, int x, int z, Runnable runnable) { + instance.drop(new QueuedChunk(x, z, null, world, null), runnable); + } + + public static void adjustPoolSize(int players) { + int size = Math.max(BASE_THREADS, (int) Math.ceil(players / PLAYERS_PER_THREAD)); + instance.setActiveThreads(size); + } + + public static void tick() { + instance.finishActive(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java new file mode 100644 index 00000000..c10c76a7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java @@ -0,0 +1,62 @@ +package org.bukkit.craftbukkit.chunkio; + +import java.io.IOException; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.AnvilChunkLoader; +import org.bukkit.craftbukkit.util.AsynchronousExecutor; + +import java.util.concurrent.atomic.AtomicInteger; + +class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider { + private final AtomicInteger threadNumber = new AtomicInteger(1); + + // async stuff + public Chunk callStage1(QueuedChunk queuedChunk) throws RuntimeException { + try { + AnvilChunkLoader loader = queuedChunk.loader; + Object[] data = loader.loadChunk__Async(queuedChunk.world, queuedChunk.x, queuedChunk.z); + + if (data != null) { + queuedChunk.compound = (NBTTagCompound) data[1]; + return (Chunk) data[0]; + } + + return null; + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + // sync stuff + public void callStage2(QueuedChunk queuedChunk, Chunk chunk) throws RuntimeException { + if (chunk == null) { + // If the chunk loading failed just do it synchronously (may generate) + queuedChunk.provider.provideChunk(queuedChunk.x, queuedChunk.z); + return; + } + + queuedChunk.loader.loadEntities(queuedChunk.world, queuedChunk.compound.getCompoundTag("Level"), chunk); + chunk.setLastSaveTime(queuedChunk.provider.world.getTotalWorldTime()); + queuedChunk.provider.id2ChunkMap.put(ChunkPos.asLong(queuedChunk.x, queuedChunk.z), chunk); + chunk.onLoad(); + + if (queuedChunk.provider.chunkGenerator != null) { + queuedChunk.provider.chunkGenerator.recreateStructures(chunk, queuedChunk.x, queuedChunk.z); + } + + chunk.populateCB(queuedChunk.provider, queuedChunk.provider.chunkGenerator, false); + } + + public void callStage3(QueuedChunk queuedChunk, Chunk chunk, Runnable runnable) throws RuntimeException { + runnable.run(); + } + + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, "Chunk I/O Executor Thread-" + threadNumber.getAndIncrement()); + thread.setDaemon(true); + return thread; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java new file mode 100644 index 00000000..ae7218d9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunk.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.chunkio; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.chunk.storage.AnvilChunkLoader; +import net.minecraft.world.gen.ChunkProviderServer; + +class QueuedChunk { + final int x; + final int z; + final AnvilChunkLoader loader; + final World world; + final ChunkProviderServer provider; + NBTTagCompound compound; + + public QueuedChunk(int x, int z, AnvilChunkLoader loader, World world, ChunkProviderServer provider) { + this.x = x; + this.z = z; + this.loader = loader; + this.world = world; + this.provider = provider; + } + + @Override + public int hashCode() { + return (x * 31 + z * 29) ^ world.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof QueuedChunk) { + QueuedChunk other = (QueuedChunk) object; + return x == other.x && z == other.z && world == other.world; + } + + return false; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java new file mode 100644 index 00000000..2a1d4aee --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java @@ -0,0 +1,78 @@ +package org.bukkit.craftbukkit.command; + +import jline.Terminal; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; +import org.fusesource.jansi.Ansi; + +import java.util.EnumMap; +import java.util.Map; + +public class ColouredConsoleSender extends CraftConsoleCommandSender { + + private static final Map replacements = new EnumMap(ChatColor.class); + static { + replacements.put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString()); + replacements.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString()); + replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString()); + replacements.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString()); + replacements.put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString()); + replacements.put(ChatColor.DARK_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString()); + replacements.put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString()); + replacements.put(ChatColor.GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString()); + replacements.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString()); + replacements.put(ChatColor.BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString()); + replacements.put(ChatColor.GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString()); + replacements.put(ChatColor.AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString()); + replacements.put(ChatColor.RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).bold().toString()); + replacements.put(ChatColor.LIGHT_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString()); + replacements.put(ChatColor.YELLOW, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString()); + replacements.put(ChatColor.WHITE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString()); + replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString()); + replacements.put(ChatColor.BOLD, Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString()); + replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString()); + replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString()); + replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString()); + replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString()); + } + + private static final ChatColor[] colors = ChatColor.values(); + private static Terminal terminal = null; + + protected ColouredConsoleSender() {} + + @Override + public void sendMessage(final String message) { + System.out.println(message); + } + + public static void setTerminal(Terminal pTerminal) { + ColouredConsoleSender.terminal = pTerminal; + } + + public static String toAnsiStr(String pMsg) { + if (terminal != null && !terminal.isAnsiSupported()) { + return ChatColor.stripColor(pMsg); + } + String result = pMsg + "§r"; + for (int length = colors.length, i = 0; i < length; ++i) { + final ChatColor color = colors[i]; + if (replacements.containsKey(color)) { + result = result.replaceAll("(?i)" + color.toString(), replacements.get(color)); + } else { + result = result.replaceAll("(?i)" + color.toString(), ""); + } + } + return result; + } + + public static ConsoleCommandSender getInstance() { + if (Bukkit.getConsoleSender() != null) { + return Bukkit.getConsoleSender(); + } else { + return new ColouredConsoleSender(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java new file mode 100644 index 00000000..3023eceb --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java @@ -0,0 +1,53 @@ +package org.bukkit.craftbukkit.command; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +import jline.console.completer.Completer; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.Waitable; +import org.bukkit.event.server.TabCompleteEvent; + +public class ConsoleCommandCompleter implements Completer { + private final CraftServer server; + + public ConsoleCommandCompleter(CraftServer server) { + this.server = server; + } + + public int complete(final String buffer, final int cursor, final List candidates) { + Waitable> waitable = new Waitable>() { + @Override + protected List evaluate() { + List offers = server.getCommandMap().tabComplete(server.getConsoleSender(), buffer); + + TabCompleteEvent tabEvent = new TabCompleteEvent(server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); + server.getPluginManager().callEvent(tabEvent); + + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); + } + }; + this.server.getServer().processQueue.add(waitable); + try { + List offers = waitable.get(); + if (offers == null) { + return cursor; + } + candidates.addAll(offers); + + final int lastSpace = buffer.lastIndexOf(' '); + if (lastSpace == -1) { + return cursor - buffer.length(); + } else { + return cursor - (buffer.length() - lastSpace - 1); + } + } catch (ExecutionException e) { + this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return cursor; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftBlockCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftBlockCommandSender.java new file mode 100644 index 00000000..6aef689a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftBlockCommandSender.java @@ -0,0 +1,51 @@ +package org.bukkit.craftbukkit.command; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.text.ITextComponent; +import org.bukkit.block.Block; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +/** + * Represents input from a command block + */ +public class CraftBlockCommandSender extends ServerCommandSender implements BlockCommandSender { + private final ICommandSender block; + + public CraftBlockCommandSender(ICommandSender commandBlockListenerAbstract) { + super(); + this.block = commandBlockListenerAbstract; + } + + public Block getBlock() { + return block.getEntityWorld().getWorld().getBlockAt(block.getPosition().getX(), block.getPosition().getY(), block.getPosition().getZ()); + } + + public void sendMessage(String message) { + for (ITextComponent component : CraftChatMessage.fromString(message)) { + block.sendMessage(component); + } + } + + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + public String getName() { + return block.getName(); + } + + public boolean isOp() { + return true; + } + + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of a block"); + } + + public ICommandSender getTileEntity() { + return block; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java new file mode 100644 index 00000000..9abcf92d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java @@ -0,0 +1,66 @@ +package org.bukkit.craftbukkit.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.conversations.ManuallyAbandonedConversationCanceller; +import org.bukkit.craftbukkit.conversations.ConversationTracker; + +/** + * Represents CLI input from a console + */ +public class CraftConsoleCommandSender extends ServerCommandSender implements ConsoleCommandSender { + + protected final ConversationTracker conversationTracker = new ConversationTracker(); + + protected CraftConsoleCommandSender() { + super(); + } + + public void sendMessage(String message) { + sendRawMessage(message); + } + + public void sendRawMessage(String message) { + System.out.println(ChatColor.stripColor(message)); + } + + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + public String getName() { + return "CONSOLE"; + } + + public boolean isOp() { + return true; + } + + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of server console"); + } + + public boolean beginConversation(Conversation conversation) { + return conversationTracker.beginConversation(conversation); + } + + public void abandonConversation(Conversation conversation) { + conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller())); + } + + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + conversationTracker.abandonConversation(conversation, details); + } + + public void acceptConversationInput(String input) { + conversationTracker.acceptConversationInput(input); + } + + public boolean isConversing() { + return conversationTracker.isConversing(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftFunctionCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftFunctionCommandSender.java new file mode 100644 index 00000000..48722e1e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftFunctionCommandSender.java @@ -0,0 +1,47 @@ +package org.bukkit.craftbukkit.command; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.text.ITextComponent; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class CraftFunctionCommandSender extends ServerCommandSender { + + private final ICommandSender handle; + + public CraftFunctionCommandSender(ICommandSender handle) { + this.handle = handle; + } + + @Override + public void sendMessage(String message) { + for (ITextComponent component : CraftChatMessage.fromString(message)) { + handle.sendMessage(component); + } + } + + @Override + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + @Override + public String getName() { + return handle.getName(); + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of server function sender"); + } + + public ICommandSender getHandle() { + return handle; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java new file mode 100644 index 00000000..411ee036 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java @@ -0,0 +1,41 @@ +package org.bukkit.craftbukkit.command; + +import net.minecraft.network.rcon.RConConsoleSource; +import net.minecraft.util.text.TextComponentString; +import org.bukkit.command.RemoteConsoleCommandSender; + +public class CraftRemoteConsoleCommandSender extends ServerCommandSender implements RemoteConsoleCommandSender { + + private final RConConsoleSource listener; + + public CraftRemoteConsoleCommandSender(RConConsoleSource listener) { + this.listener = listener; + } + + @Override + public void sendMessage(String message) { + listener.sendMessage(new TextComponentString(message + "\n")); // Send a newline after each message, to preserve formatting. + } + + @Override + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + @Override + public String getName() { + return "Rcon"; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of remote controller."); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftSimpleCommandMap.java b/src/main/java/org/bukkit/craftbukkit/command/CraftSimpleCommandMap.java new file mode 100644 index 00000000..d2349c9d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftSimpleCommandMap.java @@ -0,0 +1,63 @@ +package org.bukkit.craftbukkit.command; + +import net.minecraft.command.ICommandSender; +import net.minecraftforge.fml.common.FMLCommonHandler; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class CraftSimpleCommandMap extends SimpleCommandMap { + private static final Pattern PATTERN_ON_SPACE = Pattern.compile(" ", Pattern.LITERAL); + private ICommandSender vanillaConsoleSender; + + public CraftSimpleCommandMap(Server server) { + super(server); + } + + /** + * {@inheritDoc} + */ + public boolean dispatch(CommandSender sender, String commandLine) throws CommandException { + String[] args = PATTERN_ON_SPACE.split(commandLine); + + if (args.length == 0) { + return false; + } + + String sentCommandLabel = args[0].toLowerCase(); + Command target = getCommand(sentCommandLabel); + + if (target == null) { + return false; + } + try { + // if command is a mod command, check permissions and route through vanilla + if (target instanceof ModCustomCommand) { + if (!target.testPermission(sender)) return true; + if (sender instanceof ConsoleCommandSender) { + FMLCommonHandler.instance().getMinecraftServerInstance().getCommandManager().executeCommand(this.vanillaConsoleSender, commandLine); + } else FMLCommonHandler.instance().getMinecraftServerInstance().getCommandManager().executeCommand(((CraftPlayer)sender).getHandle(), commandLine); + } else { + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) + target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length)); //TODO testing Arrays.copyOfRange().. + } + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex); + } + // return true as command was handled + return true; + } + + public void setVanillaConsoleSender(ICommandSender console) { + this.vanillaConsoleSender = console; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/ModCustomCommand.java b/src/main/java/org/bukkit/craftbukkit/command/ModCustomCommand.java new file mode 100644 index 00000000..432a4335 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/ModCustomCommand.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.command; + +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +public class ModCustomCommand extends Command { + + public ModCustomCommand(String name) { + super(name); + } + + public ModCustomCommand(String name, String description, String usageMessage, List aliases) { + super(name, description, usageMessage, aliases); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + // Dummy method + return false; //TODO test this method + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/command/ProxiedNativeCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ProxiedNativeCommandSender.java new file mode 100644 index 00000000..4032050f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/ProxiedNativeCommandSender.java @@ -0,0 +1,133 @@ + +package org.bukkit.craftbukkit.command; + +import java.util.Set; + +import net.minecraft.command.ICommandSender; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ProxiedCommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +public class ProxiedNativeCommandSender implements ProxiedCommandSender { + + private final ICommandSender orig; + private final CommandSender caller; + private final CommandSender callee; + + public ProxiedNativeCommandSender(ICommandSender orig, CommandSender caller, CommandSender callee) { + this.orig = orig; + this.caller = caller; + this.callee = callee; + } + + public ICommandSender getHandle() { + return orig; + } + + @Override + public CommandSender getCaller() { + return caller; + } + + @Override + public CommandSender getCallee() { + return callee; + } + + @Override + public void sendMessage(String message) { + getCaller().sendMessage(message); + } + + @Override + public void sendMessage(String[] messages) { + getCaller().sendMessage(messages); + } + + @Override + public Server getServer() { + return getCallee().getServer(); + } + + @Override + public String getName() { + return getCallee().getName(); + } + + @Override + public boolean isPermissionSet(String name) { + return getCaller().isPermissionSet(name); + } + + @Override + public boolean isPermissionSet(Permission perm) { + return getCaller().isPermissionSet(perm); + } + + @Override + public boolean hasPermission(String name) { + return getCaller().hasPermission(name); + } + + @Override + public boolean hasPermission(Permission perm) { + return getCaller().hasPermission(perm); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return getCaller().addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return getCaller().addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return getCaller().addAttachment(plugin, name, value, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return getCaller().addAttachment(plugin, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + getCaller().removeAttachment(attachment); + } + + @Override + public void recalculatePermissions() { + getCaller().recalculatePermissions(); + } + + @Override + public Set getEffectivePermissions() { + return getCaller().getEffectivePermissions(); + } + + @Override + public boolean isOp() { + return getCaller().isOp(); + } + + @Override + public void setOp(boolean value) { + getCaller().setOp(value); + } + + // Spigot start + @Override + public Spigot spigot() + { + return getCaller().spigot(); + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java new file mode 100644 index 00000000..64e51cd0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java @@ -0,0 +1,94 @@ +package org.bukkit.craftbukkit.command; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; + +public abstract class ServerCommandSender implements CommandSender { + private final PermissibleBase perm = new PermissibleBase(this); + + public ServerCommandSender() { + } + + public boolean isPermissionSet(String name) { + return perm.isPermissionSet(name); + } + + public boolean isPermissionSet(Permission perm) { + return this.perm.isPermissionSet(perm); + } + + public boolean hasPermission(String name) { + return perm.hasPermission(name); + } + + public boolean hasPermission(Permission perm) { + return this.perm.hasPermission(perm); + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return perm.addAttachment(plugin, name, value); + } + + public PermissionAttachment addAttachment(Plugin plugin) { + return perm.addAttachment(plugin); + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return perm.addAttachment(plugin, name, value, ticks); + } + + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return perm.addAttachment(plugin, ticks); + } + + public void removeAttachment(PermissionAttachment attachment) { + perm.removeAttachment(attachment); + } + + public void recalculatePermissions() { + perm.recalculatePermissions(); + } + + public Set getEffectivePermissions() { + return perm.getEffectivePermissions(); + } + + public boolean isPlayer() { + return false; + } + + public Server getServer() { + return Bukkit.getServer(); + } + + // Spigot start + private final Spigot spigot = new Spigot() + { + @Override + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent component) + { + ServerCommandSender.this.sendMessage(net.md_5.bungee.api.chat.TextComponent.toLegacyText(component)); + } + + @Override + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent... components) + { + ServerCommandSender.this.sendMessage(net.md_5.bungee.api.chat.TextComponent.toLegacyText(components)); + } + }; + + @Override + public Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/UnknownCommandEvent.java b/src/main/java/org/bukkit/craftbukkit/command/UnknownCommandEvent.java new file mode 100644 index 00000000..6b141f21 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/UnknownCommandEvent.java @@ -0,0 +1,76 @@ +package org.bukkit.craftbukkit.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +/** + * Thrown when a player executes a command that is not defined + */ +public class UnknownCommandEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private CommandSender sender; + private String commandLine; + private String message; + + public UnknownCommandEvent(final CommandSender sender, final String commandLine, final String message) { + super(false); + this.sender = sender; + this.commandLine = commandLine; + this.message = message; + } + + /** + * Gets the CommandSender or ConsoleCommandSender + *

+ * + * @return Sender of the command + */ + public CommandSender getSender() { + return sender; + } + + /** + * Gets the command that was send + *

+ * + * @return Command sent + */ + public String getCommandLine() { + return commandLine; + } + + /** + * Gets message that will be returned + *

+ * + * @return Unknown command message + */ + @Nullable + public String getMessage() { + return message; + } + + + /** + * Sets message that will be returned + *

+ * Set to null to avoid any message being sent + * + * @param message the message to be returned, or null + */ + public void setMessage(String message) { + this.message = message; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java new file mode 100644 index 00000000..91fa78bd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java @@ -0,0 +1,184 @@ +package org.bukkit.craftbukkit.command; + +import java.util.Iterator; +import java.util.List; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.CommandResultStats; +import net.minecraft.command.EntitySelector; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import net.minecraft.server.*; + +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.tileentity.CommandBlockBaseLogic; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.WorldServer; +import org.apache.commons.lang3.Validate; +import org.apache.logging.log4j.Level; +import org.bukkit.Location; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.ProxiedCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftMinecartCommand; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.entity.minecart.CommandMinecart; + +public final class VanillaCommandWrapper extends BukkitCommand { + protected final CommandBase vanillaCommand; + + public VanillaCommandWrapper(CommandBase vanillaCommand, String usage) { + super(vanillaCommand.getName(), "A Mojang provided command.", usage, vanillaCommand.getAliases()); + this.vanillaCommand = vanillaCommand; + this.setPermission("minecraft.command." + vanillaCommand.getName()); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + + ICommandSender icommandlistener = getListener(sender); + try { + dispatchVanillaCommand(sender, icommandlistener, args); + } catch (CommandException commandexception) { + // Taken from CommandHandler + TextComponentTranslation chatmessage = new TextComponentTranslation(commandexception.getMessage(), commandexception.getErrorObjects()); + chatmessage.getStyle().setColor(TextFormatting.RED); + icommandlistener.sendMessage(chatmessage); + } + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + return (List) vanillaCommand.getTabCompletions(MinecraftServer.getServerCB(), getListener(sender), args, (location) == null ? null : new BlockPos(location.getX(), location.getY(), location.getZ())); + } + + public static CommandSender lastSender = null; // Nasty :( + + public final int dispatchVanillaCommand(CommandSender bSender, ICommandSender icommandlistener, String[] as) throws CommandException { + // Copied from net.minecraft.server.CommandHandler + int i = getPlayerListSize(as); + int j = 0; + // Some commands use the worldserver variable but we leave it full of null values, + // so we must temporarily populate it with the world of the commandsender + MinecraftServer server = MinecraftServer.getServerInstance(); + try { + if (vanillaCommand.checkPermission(server, icommandlistener)) { + if (i > -1) { + List list = ((List) EntitySelector.matchEntitiesDefault(icommandlistener, as[i], Entity.class)); + String s2 = as[i]; + + icommandlistener.setCommandStat(CommandResultStats.Type.AFFECTED_ENTITIES, list.size()); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + Entity entity = iterator.next(); + + CommandSender oldSender = lastSender; + lastSender = bSender; + try { + as[i] = entity.getUniqueID().toString(); + vanillaCommand.execute(server, icommandlistener, as); + j++; + } catch (WrongUsageException exceptionusage) { + TextComponentTranslation chatmessage = new TextComponentTranslation("commands.generic.usage", new Object[] { new TextComponentTranslation(exceptionusage.getMessage(), exceptionusage.getErrorObjects())}); + chatmessage.getStyle().setColor(TextFormatting.RED); + icommandlistener.sendMessage(chatmessage); + } catch (CommandException commandexception) { + CommandBase.notifyCommandListener(icommandlistener, vanillaCommand, 0, commandexception.getMessage(), commandexception.getErrorObjects()); + } finally { + lastSender = oldSender; + } + } + as[i] = s2; + } else { + icommandlistener.setCommandStat(CommandResultStats.Type.AFFECTED_ENTITIES, 1); + vanillaCommand.execute(server, icommandlistener, as); + j++; + } + } else { + TextComponentTranslation chatmessage = new TextComponentTranslation("commands.generic.permission", new Object[0]); + chatmessage.getStyle().setColor(TextFormatting.RED); + icommandlistener.sendMessage(chatmessage); + } + } catch (WrongUsageException exceptionusage) { + TextComponentTranslation chatmessage1 = new TextComponentTranslation("commands.generic.usage", new Object[] { new TextComponentTranslation(exceptionusage.getMessage(), exceptionusage.getErrorObjects()) }); + chatmessage1.getStyle().setColor(TextFormatting.RED); + icommandlistener.sendMessage(chatmessage1); + } catch (CommandException commandexception) { + CommandBase.notifyCommandListener(icommandlistener, vanillaCommand, 0, commandexception.getMessage(), commandexception.getErrorObjects()); + } catch (Throwable throwable) { + TextComponentTranslation chatmessage3 = new TextComponentTranslation("commands.generic.exception", new Object[0]); + chatmessage3.getStyle().setColor(TextFormatting.RED); + icommandlistener.sendMessage(chatmessage3); + if (icommandlistener.getCommandSenderEntity() instanceof EntityMinecartCommandBlock) { + MinecraftServer.LOGGER.log(Level.WARN, String.format("MinecartCommandBlock at (%d,%d,%d) failed to handle command", icommandlistener.getPosition().getX(), icommandlistener.getPosition().getY(), icommandlistener.getPosition().getZ()), throwable); + } else if(icommandlistener instanceof CommandBlockBaseLogic) { + CommandBlockBaseLogic listener = (CommandBlockBaseLogic) icommandlistener; + MinecraftServer.LOGGER.log(Level.WARN, String.format("CommandBlock at (%d,%d,%d) failed to handle command", listener.getPosition().getX(), listener.getPosition().getY(), listener.getPosition().getZ()), throwable); + } else { + MinecraftServer.LOGGER.log(Level.WARN, String.format("Unknown CommandBlock failed to handle command"), throwable); + } + } finally { + icommandlistener.setCommandStat(CommandResultStats.Type.SUCCESS_COUNT, j); + } + return j; + } + + private ICommandSender getListener(CommandSender sender) { + if (sender instanceof Player) { + return ((CraftPlayer) sender).getHandle(); + } + if (sender instanceof BlockCommandSender) { + return ((CraftBlockCommandSender) sender).getTileEntity(); + } + if (sender instanceof CommandMinecart) { + return ((EntityMinecartCommandBlock) ((CraftMinecartCommand) sender).getHandle()).getCommandBlockLogic(); + } + if (sender instanceof RemoteConsoleCommandSender) { + return ((DedicatedServer)MinecraftServer.getServerCB()).rconConsoleSource; + } + if (sender instanceof ConsoleCommandSender) { + return ((CraftServer) sender.getServer()).getServer(); + } + if (sender instanceof ProxiedCommandSender) { + return ((ProxiedNativeCommandSender) sender).getHandle(); + } + if (sender instanceof CraftFunctionCommandSender) { + return ((CraftFunctionCommandSender) sender).getHandle(); + } + throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); + } + + private int getPlayerListSize(String as[]) throws CommandException { + for (int i = 0; i < as.length; i++) { + if (vanillaCommand.isUsernameIndex(as, i) && EntitySelector.matchesMultiplePlayersDefault(as[i])) { + return i; + } + } + return -1; + } + + public static String[] dropFirstArgument(String as[]) { + String as1[] = new String[as.length - 1]; + for (int i = 1; i < as.length; i++) { + as1[i - 1] = as[i]; + } + + return as1; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/conversations/ConversationTracker.java b/src/main/java/org/bukkit/craftbukkit/conversations/ConversationTracker.java new file mode 100644 index 00000000..eefa68a6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/conversations/ConversationTracker.java @@ -0,0 +1,77 @@ +package org.bukkit.craftbukkit.conversations; + +import java.util.LinkedList; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.conversations.ManuallyAbandonedConversationCanceller; + +/** + */ +public class ConversationTracker { + + private LinkedList conversationQueue = new LinkedList(); + + public synchronized boolean beginConversation(Conversation conversation) { + if (!conversationQueue.contains(conversation)) { + conversationQueue.addLast(conversation); + if (conversationQueue.getFirst() == conversation) { + conversation.begin(); + conversation.outputNextPrompt(); + return true; + } + } + return true; + } + + public synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + if (!conversationQueue.isEmpty()) { + if (conversationQueue.getFirst() == conversation) { + conversation.abandon(details); + } + if (conversationQueue.contains(conversation)) { + conversationQueue.remove(conversation); + } + if (!conversationQueue.isEmpty()) { + conversationQueue.getFirst().outputNextPrompt(); + } + } + } + + public synchronized void abandonAllConversations() { + + LinkedList oldQueue = conversationQueue; + conversationQueue = new LinkedList(); + for (Conversation conversation : oldQueue) { + try { + conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller())); + } catch (Throwable t) { + Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", t); + } + } + } + + public synchronized void acceptConversationInput(String input) { + if (isConversing()) { + Conversation conversation = conversationQueue.getFirst(); + try { + conversation.acceptInput(input); + } catch (Throwable t) { + conversation.getContext().getPlugin().getLogger().log(Level.WARNING, + String.format("Plugin %s generated an exception whilst handling conversation input", + conversation.getContext().getPlugin().getDescription().getFullName() + ), t); + } + } + } + + public synchronized boolean isConversing() { + return !conversationQueue.isEmpty(); + } + + public synchronized boolean isConversingModaly() { + return isConversing() && conversationQueue.getFirst().isModal(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java new file mode 100644 index 00000000..5db5ec39 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java @@ -0,0 +1,169 @@ +package org.bukkit.craftbukkit.enchantments; + +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.enchantments.EnchantmentWrapper; +import org.bukkit.inventory.ItemStack; + +public class CraftEnchantment extends Enchantment { + private final net.minecraft.enchantment.Enchantment target; + + public CraftEnchantment(net.minecraft.enchantment.Enchantment target) { + super(net.minecraft.enchantment.Enchantment.getEnchantmentID(target)); + this.target = target; + } + + @Override + public int getMaxLevel() { + return target.getMaxLevel(); + } + + @Override + public int getStartLevel() { + return target.getMinLevel(); + } + + @Override + public EnchantmentTarget getItemTarget() { + switch (target.type) { + case ALL: + return EnchantmentTarget.ALL; + case ARMOR: + return EnchantmentTarget.ARMOR; + case ARMOR_FEET: + return EnchantmentTarget.ARMOR_FEET; + case ARMOR_HEAD: + return EnchantmentTarget.ARMOR_HEAD; + case ARMOR_LEGS: + return EnchantmentTarget.ARMOR_LEGS; + case ARMOR_CHEST: + return EnchantmentTarget.ARMOR_TORSO; + case DIGGER: + return EnchantmentTarget.TOOL; + case WEAPON: + return EnchantmentTarget.WEAPON; + case BOW: + return EnchantmentTarget.BOW; + case FISHING_ROD: + return EnchantmentTarget.FISHING_ROD; + case BREAKABLE: + return EnchantmentTarget.BREAKABLE; + case WEARABLE: + return EnchantmentTarget.WEARABLE; + default: + return null; + } + } + + @Override + public boolean isTreasure() { + return target.isTreasureEnchantment(); + } + + @Override + public boolean isCursed() { + return target.isCurse(); + } + + @Override + public boolean canEnchantItem(ItemStack item) { + return target.canApply(CraftItemStack.asNMSCopy(item)); + } + + @Override + public String getName() { + switch (getId()) { + case 0: + return "PROTECTION_ENVIRONMENTAL"; + case 1: + return "PROTECTION_FIRE"; + case 2: + return "PROTECTION_FALL"; + case 3: + return "PROTECTION_EXPLOSIONS"; + case 4: + return "PROTECTION_PROJECTILE"; + case 5: + return "OXYGEN"; + case 6: + return "WATER_WORKER"; + case 7: + return "THORNS"; + case 8: + return "DEPTH_STRIDER"; + case 9: + return "FROST_WALKER"; + case 10: + return "BINDING_CURSE"; + case 16: + return "DAMAGE_ALL"; + case 17: + return "DAMAGE_UNDEAD"; + case 18: + return "DAMAGE_ARTHROPODS"; + case 19: + return "KNOCKBACK"; + case 20: + return "FIRE_ASPECT"; + case 21: + return "LOOT_BONUS_MOBS"; + case 22: + return "SWEEPING_EDGE"; + case 32: + return "DIG_SPEED"; + case 33: + return "SILK_TOUCH"; + case 34: + return "DURABILITY"; + case 35: + return "LOOT_BONUS_BLOCKS"; + case 48: + return "ARROW_DAMAGE"; + case 49: + return "ARROW_KNOCKBACK"; + case 50: + return "ARROW_FIRE"; + case 51: + return "ARROW_INFINITE"; + case 61: + return "LUCK"; + case 62: + return "LURE"; + case 70: + return "MENDING"; + case 71: + return "VANISHING_CURSE"; + default: + return "UNKNOWN_ENCHANT_" + getId(); + } + } + + public static net.minecraft.enchantment.Enchantment getRaw(Enchantment enchantment) { + if (enchantment instanceof EnchantmentWrapper) { + enchantment = ((EnchantmentWrapper) enchantment).getEnchantment(); + } + + if (enchantment instanceof CraftEnchantment) { + return ((CraftEnchantment) enchantment).target; + } + + return null; + } + + @Override + public boolean conflictsWith(Enchantment other) { + if (other instanceof EnchantmentWrapper) { + other = ((EnchantmentWrapper) other).getEnchantment(); + } + if (!(other instanceof CraftEnchantment)) { + return false; + } + CraftEnchantment ench = (CraftEnchantment) other; + return !target.isCompatibleWith(ench.target); + } + + public net.minecraft.enchantment.Enchantment getHandle() { + return target; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java new file mode 100644 index 00000000..7773624d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.entity; + +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Projectile; + +public abstract class AbstractProjectile extends CraftEntity implements Projectile { + + private boolean doesBounce; + + public AbstractProjectile(CraftServer server, net.minecraft.entity.Entity entity) { + super(server, entity); + doesBounce = false; + } + + public boolean doesBounce() { + return doesBounce; + } + + public void setBounce(boolean doesBounce) { + this.doesBounce = doesBounce; + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java new file mode 100644 index 00000000..62a524a7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java @@ -0,0 +1,96 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.UUID; +import net.minecraft.entity.passive.EntityHorse; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventoryAbstractHorse; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Horse; +import org.bukkit.inventory.AbstractHorseInventory; + +public abstract class CraftAbstractHorse extends CraftAnimals implements AbstractHorse { + + public CraftAbstractHorse(CraftServer server, net.minecraft.entity.passive.AbstractHorse entity) { + super(server, entity); + } + + @Override + public net.minecraft.entity.passive.AbstractHorse getHandle() { + return (net.minecraft.entity.passive.AbstractHorse) entity; + } + + public void setVariant(Horse.Variant variant) { + throw new UnsupportedOperationException("Not supported."); + } + + public int getDomestication() { + return getHandle().getTemper(); + } + + public void setDomestication(int value) { + Validate.isTrue(value >= 0, "Domestication cannot be less than zero"); + Validate.isTrue(value <= getMaxDomestication(), "Domestication cannot be greater than the max domestication"); + getHandle().setTemper(value); + } + + public int getMaxDomestication() { + return getHandle().getMaxTemper(); + } + + public void setMaxDomestication(int value) { + Validate.isTrue(value > 0, "Max domestication cannot be zero or less"); + getHandle().maxDomestication = value; + } + + public double getJumpStrength() { + return getHandle().getHorseJumpStrength(); + } + + public void setJumpStrength(double strength) { + Validate.isTrue(strength >= 0, "Jump strength cannot be less than zero"); + getHandle().getEntityAttribute(EntityHorse.JUMP_STRENGTH).setBaseValue(strength); + } + + @Override + public boolean isTamed() { + return getHandle().isTame(); + } + + @Override + public void setTamed(boolean tamed) { + getHandle().setHorseTamed(tamed); + } + + @Override + public AnimalTamer getOwner() { + if (getOwnerUUID() == null) return null; + return getServer().getOfflinePlayer(getOwnerUUID()); + } + + @Override + public void setOwner(AnimalTamer owner) { + if (owner != null) { + setTamed(true); + getHandle().setAttackTarget(null, null, false); + setOwnerUUID(owner.getUniqueId()); + } else { + setTamed(false); + setOwnerUUID(null); + } + } + + public UUID getOwnerUUID() { + return getHandle().getOwnerUniqueId(); + } + + public void setOwnerUUID(UUID uuid) { + getHandle().setOwnerUniqueId(uuid); + } + + @Override + public AbstractHorseInventory getInventory() { + return new CraftInventoryAbstractHorse(getHandle().horseChest); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java new file mode 100644 index 00000000..77c3ff34 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java @@ -0,0 +1,66 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityAgeable; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Ageable; + +public class CraftAgeable extends CraftCreature implements Ageable { + public CraftAgeable(CraftServer server, EntityAgeable entity) { + super(server, entity); + } + + public int getAge() { + return getHandle().getGrowingAge(); + } + + public void setAge(int age) { + getHandle().setGrowingAge(age); + } + + public void setAgeLock(boolean lock) { + getHandle().ageLocked = lock; + } + + public boolean getAgeLock() { + return getHandle().ageLocked; + } + + public void setBaby() { + if (isAdult()) { + setAge(-24000); + } + } + + public void setAdult() { + if (!isAdult()) { + setAge(0); + } + } + + public boolean isAdult() { + return getAge() >= 0; + } + + + public boolean canBreed() { + return getAge() == 0; + } + + public void setBreed(boolean breed) { + if (breed) { + setAge(0); + } else if (isAdult()) { + setAge(6000); + } + } + + @Override + public EntityAgeable getHandle() { + return (EntityAgeable) entity; + } + + @Override + public String toString() { + return "CraftAgeable"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java new file mode 100644 index 00000000..cf02c6d4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityAmbientCreature; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Ambient; +import org.bukkit.entity.EntityType; + +public class CraftAmbient extends CraftLivingEntity implements Ambient { + public CraftAmbient(CraftServer server, EntityAmbientCreature entity) { + super(server, entity); + } + + @Override + public EntityAmbientCreature getHandle() { + return (EntityAmbientCreature) entity; + } + + @Override + public String toString() { + return "CraftAmbient"; + } + + public EntityType getType() { + return EntityType.UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java new file mode 100644 index 00000000..5396d283 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityAnimal; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Animals; + +public class CraftAnimals extends CraftAgeable implements Animals { + + public CraftAnimals(CraftServer server, EntityAnimal entity) { + super(server, entity); + } + + @Override + public EntityAnimal getHandle() { + return (EntityAnimal) entity; + } + + @Override + public String toString() { + return "CraftAnimals"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java new file mode 100644 index 00000000..02c9c692 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java @@ -0,0 +1,221 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.List; + +import net.minecraft.entity.EntityAreaEffectCloud; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.potion.Potion; +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.craftbukkit.CraftParticle; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.potion.PotionData; + +import com.google.common.collect.ImmutableList; + +public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud { + + public CraftAreaEffectCloud(CraftServer server, EntityAreaEffectCloud entity) { + super(server, entity); + } + + @Override + public EntityAreaEffectCloud getHandle() { + return (EntityAreaEffectCloud) super.getHandle(); + } + + @Override + public EntityType getType() { + return EntityType.AREA_EFFECT_CLOUD; + } + + @Override + public int getDuration() { + return getHandle().getDuration(); + } + + @Override + public void setDuration(int duration) { + getHandle().setDuration(duration); + } + + @Override + public int getWaitTime() { + return getHandle().waitTime; + } + + @Override + public void setWaitTime(int waitTime) { + getHandle().setWaitTime(waitTime); + } + + @Override + public int getReapplicationDelay() { + return getHandle().reapplicationDelay; + } + + @Override + public void setReapplicationDelay(int delay) { + getHandle().reapplicationDelay = delay; + } + + @Override + public int getDurationOnUse() { + return getHandle().durationOnUse; + } + + @Override + public void setDurationOnUse(int duration) { + getHandle().durationOnUse = duration; + } + + @Override + public float getRadius() { + return getHandle().getRadius(); + } + + @Override + public void setRadius(float radius) { + getHandle().setRadius(radius); + } + + @Override + public float getRadiusOnUse() { + return getHandle().radiusOnUse; + } + + @Override + public void setRadiusOnUse(float radius) { + getHandle().setRadiusOnUse(radius); + } + + @Override + public float getRadiusPerTick() { + return getHandle().radiusPerTick; + } + + @Override + public void setRadiusPerTick(float radius) { + getHandle().setRadiusPerTick(radius); + } + + @Override + public Particle getParticle() { + return CraftParticle.toBukkit(getHandle().getParticle()); + } + + @Override + public void setParticle(Particle particle) { + getHandle().setParticle(CraftParticle.toNMS(particle)); + } + + @Override + public Color getColor() { + return Color.fromRGB(getHandle().getColor()); + } + + @Override + public void setColor(Color color) { + getHandle().setColor(color.asRGB()); + } + + @Override + public boolean addCustomEffect(PotionEffect effect, boolean override) { + int effectId = effect.getType().getId(); + net.minecraft.potion.PotionEffect existing = null; + for (net.minecraft.potion.PotionEffect mobEffect : getHandle().effects) { + if (Potion.getIdFromPotion(mobEffect.getPotion()) == effectId) { + existing = mobEffect; + } + } + if (existing != null) { + if (!override) { + return false; + } + getHandle().effects.remove(existing); + } + getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); + getHandle().refreshEffects(); + return true; + } + + @Override + public void clearCustomEffects() { + getHandle().effects.clear(); + getHandle().refreshEffects(); + } + + @Override + public List getCustomEffects() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (net.minecraft.potion.PotionEffect effect : getHandle().effects) { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + return builder.build(); + } + + @Override + public boolean hasCustomEffect(PotionEffectType type) { + for (net.minecraft.potion.PotionEffect effect : getHandle().effects) { + if (CraftPotionUtil.equals(effect.getPotion(), type)) { + return true; + } + } + return false; + } + + @Override + public boolean hasCustomEffects() { + return !getHandle().effects.isEmpty(); + } + + @Override + public boolean removeCustomEffect(PotionEffectType effect) { + int effectId = effect.getId(); + net.minecraft.potion.PotionEffect existing = null; + for (net.minecraft.potion.PotionEffect mobEffect : getHandle().effects) { + if (Potion.getIdFromPotion(mobEffect.getPotion()) == effectId) { + existing = mobEffect; + } + } + if (existing == null) { + return false; + } + getHandle().effects.remove(existing); + getHandle().refreshEffects(); + return true; + } + + @Override + public void setBasePotionData(PotionData data) { + Validate.notNull(data, "PotionData cannot be null"); + getHandle().setType(CraftPotionUtil.fromBukkit(data)); + } + + @Override + public PotionData getBasePotionData() { + return CraftPotionUtil.toBukkit(getHandle().getType()); + } + + public ProjectileSource getSource() { + EntityLivingBase source = getHandle().getOwner(); + return (source == null) ? null : (LivingEntity) source.getBukkitEntity(); + } + + public void setSource(ProjectileSource shooter) { + if (shooter instanceof CraftLivingEntity) { + getHandle().setOwner((EntityLiving) ((CraftLivingEntity) shooter).getHandle()); + } else { + getHandle().setOwner((EntityLiving) null); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java new file mode 100644 index 00000000..35b18633 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java @@ -0,0 +1,214 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.util.math.Rotations; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.EulerAngle; + +public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + + public CraftArmorStand(CraftServer server, EntityArmorStand entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftArmorStand"; + } + + @Override + public EntityType getType() { + return EntityType.ARMOR_STAND; + } + + @Override + public EntityArmorStand getHandle() { + return (EntityArmorStand) super.getHandle(); + } + + @Override + public ItemStack getItemInHand() { + return getEquipment().getItemInHand(); + } + + @Override + public void setItemInHand(ItemStack item) { + getEquipment().setItemInHand(item); + } + + @Override + public ItemStack getBoots() { + return getEquipment().getBoots(); + } + + @Override + public void setBoots(ItemStack item) { + getEquipment().setBoots(item); + } + + @Override + public ItemStack getLeggings() { + return getEquipment().getLeggings(); + } + + @Override + public void setLeggings(ItemStack item) { + getEquipment().setLeggings(item); + } + + @Override + public ItemStack getChestplate() { + return getEquipment().getChestplate(); + } + + @Override + public void setChestplate(ItemStack item) { + getEquipment().setChestplate(item); + } + + @Override + public ItemStack getHelmet() { + return getEquipment().getHelmet(); + } + + @Override + public void setHelmet(ItemStack item) { + getEquipment().setHelmet(item); + } + + @Override + public EulerAngle getBodyPose() { + return fromNMS(getHandle().bodyRotation); + } + + @Override + public void setBodyPose(EulerAngle pose) { + getHandle().setBodyRotation(toNMS(pose)); + } + + @Override + public EulerAngle getLeftArmPose() { + return fromNMS(getHandle().leftArmRotation); + } + + @Override + public void setLeftArmPose(EulerAngle pose) { + getHandle().setLeftArmRotation(toNMS(pose)); + } + + @Override + public EulerAngle getRightArmPose() { + return fromNMS(getHandle().rightArmRotation); + } + + @Override + public void setRightArmPose(EulerAngle pose) { + getHandle().setRightArmRotation(toNMS(pose)); + } + + @Override + public EulerAngle getLeftLegPose() { + return fromNMS(getHandle().leftLegRotation); + } + + @Override + public void setLeftLegPose(EulerAngle pose) { + getHandle().setLeftLegRotation(toNMS(pose)); + } + + @Override + public EulerAngle getRightLegPose() { + return fromNMS(getHandle().rightLegRotation); + } + + @Override + public void setRightLegPose(EulerAngle pose) { + getHandle().setRightLegRotation(toNMS(pose)); + } + + @Override + public EulerAngle getHeadPose() { + return fromNMS(getHandle().headRotation); + } + + @Override + public void setHeadPose(EulerAngle pose) { + getHandle().setHeadRotation(toNMS(pose)); + } + + @Override + public boolean hasBasePlate() { + return !getHandle().hasNoBasePlate(); + } + + @Override + public void setBasePlate(boolean basePlate) { + getHandle().setNoBasePlate(!basePlate); + } + + @Override + public void setGravity(boolean gravity) { + super.setGravity(gravity); + // Armor stands are special + getHandle().noClip = !gravity; + } + + @Override + public boolean isVisible() { + return !getHandle().isInvisible(); + } + + @Override + public void setVisible(boolean visible) { + getHandle().setInvisible(!visible); + } + + @Override + public boolean hasArms() { + return getHandle().getShowArms(); + } + + @Override + public void setArms(boolean arms) { + getHandle().setShowArms(arms); + } + + @Override + public boolean isSmall() { + return getHandle().isSmall(); + } + + @Override + public void setSmall(boolean small) { + getHandle().setSmall(small); + } + + private static EulerAngle fromNMS(Rotations old) { + return new EulerAngle( + Math.toRadians(old.getX()), + Math.toRadians(old.getY()), + Math.toRadians(old.getZ()) + ); + } + + private static Rotations toNMS(EulerAngle old) { + return new Rotations( + (float) Math.toDegrees(old.getX()), + (float) Math.toDegrees(old.getY()), + (float) Math.toDegrees(old.getZ()) + ); + } + + @Override + public boolean isMarker() { + return getHandle().hasMarker(); + } + + @Override + public void setMarker(boolean marker) { + getHandle().setMarker(marker); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java new file mode 100644 index 00000000..076194bf --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java @@ -0,0 +1,111 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; + +import net.minecraft.entity.projectile.EntityArrow; +import org.apache.commons.lang3.Validate; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.projectiles.ProjectileSource; + +public class CraftArrow extends AbstractProjectile implements Arrow { + + public CraftArrow(CraftServer server, EntityArrow entity) { + super(server, entity); + } + + public void setKnockbackStrength(int knockbackStrength) { + Validate.isTrue(knockbackStrength >= 0, "Knockback cannot be negative"); + getHandle().setKnockbackStrength(knockbackStrength); + } + + public int getKnockbackStrength() { + return getHandle().knockbackStrength; + } + + public boolean isCritical() { + return getHandle().getIsCritical(); + } + + public void setCritical(boolean critical) { + getHandle().setIsCritical(critical); + } + + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + public void setShooter(ProjectileSource shooter) { + if (shooter instanceof LivingEntity) { + getHandle().shootingEntity = ((CraftLivingEntity) shooter).getHandle(); + } else { + getHandle().shootingEntity = null; + } + getHandle().projectileSource = shooter; + } + + @Override + public boolean isInBlock() { + return getHandle().onGround; + } + + @Override + public Block getAttachedBlock() { + if (!isInBlock()) { + return null; + } + + EntityArrow handle = getHandle(); + return getWorld().getBlockAt(handle.xTile, handle.yTile, handle.zTile); + } + + @Override + public PickupStatus getPickupStatus() { + return PickupStatus.values()[getHandle().pickupStatus.ordinal()]; + } + + @Override + public void setPickupStatus(PickupStatus status) { + Preconditions.checkNotNull(status, "status"); + getHandle().pickupStatus = EntityArrow.PickupStatus.getByOrdinal(status.ordinal()); + } + + @Override + public EntityArrow getHandle() { + return (EntityArrow) entity; + } + + @Override + public String toString() { + return "CraftArrow"; + } + + public EntityType getType() { + return EntityType.ARROW; + } + + // Spigot start + private final Arrow.Spigot spigot = new Arrow.Spigot() + { + @Override + public double getDamage() + { + return getHandle().getDamage(); + } + + @Override + public void setDamage(double damage) + { + getHandle().setDamage( damage ); + } + }; + + public Arrow.Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java new file mode 100644 index 00000000..bea942df --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityBat; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Bat; +import org.bukkit.entity.EntityType; + +public class CraftBat extends CraftAmbient implements Bat { + public CraftBat(CraftServer server, EntityBat entity) { + super(server, entity); + } + + @Override + public EntityBat getHandle() { + return (EntityBat) entity; + } + + @Override + public String toString() { + return "CraftBat"; + } + + public EntityType getType() { + return EntityType.BAT; + } + + @Override + public boolean isAwake() { + return !getHandle().getIsBatHanging(); + } + + @Override + public void setAwake(boolean state) { + getHandle().setIsBatHanging(!state); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java new file mode 100644 index 00000000..a766e19e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityBlaze; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Blaze; +import org.bukkit.entity.EntityType; + +public class CraftBlaze extends CraftMonster implements Blaze { + public CraftBlaze(CraftServer server, EntityBlaze entity) { + super(server, entity); + } + + @Override + public EntityBlaze getHandle() { + return (EntityBlaze) entity; + } + + @Override + public String toString() { + return "CraftBlaze"; + } + + public EntityType getType() { + return EntityType.BLAZE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java new file mode 100644 index 00000000..304a6a5b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java @@ -0,0 +1,110 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityBoat; +import org.bukkit.TreeSpecies; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Boat; +import org.bukkit.entity.EntityType; + +public class CraftBoat extends CraftVehicle implements Boat { + + public CraftBoat(CraftServer server, EntityBoat entity) { + super(server, entity); + } + + @Override + public TreeSpecies getWoodType() { + return getTreeSpecies(getHandle().getBoatType()); + } + + @Override + public void setWoodType(TreeSpecies species) { + getHandle().setBoatType(getBoatType(species)); + } + + public double getMaxSpeed() { + return getHandle().maxSpeed; + } + + public void setMaxSpeed(double speed) { + if (speed >= 0D) { + getHandle().maxSpeed = speed; + } + } + + public double getOccupiedDeceleration() { + return getHandle().occupiedDeceleration; + } + + public void setOccupiedDeceleration(double speed) { + if (speed >= 0D) { + getHandle().occupiedDeceleration = speed; + } + } + + public double getUnoccupiedDeceleration() { + return getHandle().unoccupiedDeceleration; + } + + public void setUnoccupiedDeceleration(double speed) { + getHandle().unoccupiedDeceleration = speed; + } + + public boolean getWorkOnLand() { + return getHandle().landBoats; + } + + public void setWorkOnLand(boolean workOnLand) { + getHandle().landBoats = workOnLand; + } + + @Override + public EntityBoat getHandle() { + return (EntityBoat) entity; + } + + @Override + public String toString() { + return "CraftBoat"; + } + + public EntityType getType() { + return EntityType.BOAT; + } + + public static TreeSpecies getTreeSpecies(EntityBoat.Type boatType) { + switch (boatType) { + case SPRUCE: + return TreeSpecies.REDWOOD; + case BIRCH: + return TreeSpecies.BIRCH; + case JUNGLE: + return TreeSpecies.JUNGLE; + case ACACIA: + return TreeSpecies.ACACIA; + case DARK_OAK: + return TreeSpecies.DARK_OAK; + case OAK: + default: + return TreeSpecies.GENERIC; + } + } + + public static EntityBoat.Type getBoatType(TreeSpecies species) { + switch (species) { + case REDWOOD: + return EntityBoat.Type.SPRUCE; + case BIRCH: + return EntityBoat.Type.BIRCH; + case JUNGLE: + return EntityBoat.Type.JUNGLE; + case ACACIA: + return EntityBoat.Type.ACACIA; + case DARK_OAK: + return EntityBoat.Type.DARK_OAK; + case GENERIC: + default: + return EntityBoat.Type.OAK; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java new file mode 100644 index 00000000..4c0db1b5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityCaveSpider; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.CaveSpider; +import org.bukkit.entity.EntityType; + +public class CraftCaveSpider extends CraftSpider implements CaveSpider { + public CraftCaveSpider(CraftServer server, EntityCaveSpider entity) { + super(server, entity); + } + + @Override + public EntityCaveSpider getHandle() { + return (EntityCaveSpider) entity; + } + + @Override + public String toString() { + return "CraftCaveSpider"; + } + + public EntityType getType() { + return EntityType.CAVE_SPIDER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java new file mode 100644 index 00000000..93b67d11 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java @@ -0,0 +1,29 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.AbstractChestHorse; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ChestedHorse; + +public abstract class CraftChestedHorse extends CraftAbstractHorse implements ChestedHorse { + + public CraftChestedHorse(CraftServer server, AbstractChestHorse entity) { + super(server, entity); + } + + @Override + public AbstractChestHorse getHandle() { + return (AbstractChestHorse) super.getHandle(); + } + + @Override + public boolean isCarryingChest() { + return getHandle().hasChest(); + } + + @Override + public void setCarryingChest(boolean chest) { + if (chest == isCarryingChest()) return; + getHandle().setChested(chest); + getHandle().initHorseChest(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java new file mode 100644 index 00000000..1971f10c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityChicken; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Chicken; +import org.bukkit.entity.EntityType; + +public class CraftChicken extends CraftAnimals implements Chicken { + + public CraftChicken(CraftServer server, EntityChicken entity) { + super(server, entity); + } + + @Override + public EntityChicken getHandle() { + return (EntityChicken) entity; + } + + @Override + public String toString() { + return "CraftChicken"; + } + + public EntityType getType() { + return EntityType.CHICKEN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java new file mode 100644 index 00000000..fe1a6d88 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java @@ -0,0 +1,21 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityLiving; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ComplexLivingEntity; + +public abstract class CraftComplexLivingEntity extends CraftLivingEntity implements ComplexLivingEntity { + public CraftComplexLivingEntity(CraftServer server, EntityLiving entity) { + super(server, entity); + } + + @Override + public EntityLiving getHandle() { + return (EntityLiving) entity; + } + + @Override + public String toString() { + return "CraftComplexLivingEntity"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java new file mode 100644 index 00000000..325d3316 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.MultiPartEntityPart; +import net.minecraft.entity.boss.EntityDragon; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ComplexEntityPart; +import org.bukkit.entity.ComplexLivingEntity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.EntityDamageEvent; + +public class CraftComplexPart extends CraftEntity implements ComplexEntityPart { + public CraftComplexPart(CraftServer server, MultiPartEntityPart entity) { + super(server, entity); + } + + public ComplexLivingEntity getParent() { + return (ComplexLivingEntity) ((EntityDragon) getHandle().parent).getBukkitEntity(); + } + + @Override + public void setLastDamageCause(EntityDamageEvent cause) { + getParent().setLastDamageCause(cause); + } + + @Override + public EntityDamageEvent getLastDamageCause() { + return getParent().getLastDamageCause(); + } + + @Override + public boolean isValid() { + return getParent().isValid(); + } + + @Override + public MultiPartEntityPart getHandle() { + return (MultiPartEntityPart) entity; + } + + @Override + public String toString() { + return "CraftComplexPart"; + } + + public EntityType getType() { + return EntityType.COMPLEX_PART; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java new file mode 100644 index 00000000..5748e3cd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityCow; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Cow; +import org.bukkit.entity.EntityType; + +public class CraftCow extends CraftAnimals implements Cow { + + public CraftCow(CraftServer server, EntityCow entity) { + super(server, entity); + } + + @Override + public EntityCow getHandle() { + return (EntityCow) entity; + } + + @Override + public String toString() { + return "CraftCow"; + } + + public EntityType getType() { + return EntityType.COW; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java new file mode 100644 index 00000000..4ed8b455 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityCreature; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Creature; +import org.bukkit.entity.LivingEntity; + +public class CraftCreature extends CraftLivingEntity implements Creature { + public CraftCreature(CraftServer server, EntityCreature entity) { + super(server, entity); + } + + public void setTarget(LivingEntity target) { + EntityCreature entity = getHandle(); + if (target == null) { + entity.setAttackTarget(null, null, false); + } else if (target instanceof CraftLivingEntity) { + entity.setAttackTarget(((CraftLivingEntity) target).getHandle(), null, false); + } + } + + public CraftLivingEntity getTarget() { + if (getHandle().getAttackTarget() == null) return null; + + return (CraftLivingEntity) getHandle().getAttackTarget().getBukkitEntity(); + } + + @Override + public EntityCreature getHandle() { + return (EntityCreature) entity; + } + + @Override + public String toString() { + return "CraftCreature"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java new file mode 100644 index 00000000..66d586f7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java @@ -0,0 +1,79 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; + +import net.minecraft.entity.monster.EntityCreeper; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.CreeperPowerEvent; + +public class CraftCreeper extends CraftMonster implements Creeper { + + public CraftCreeper(CraftServer server, EntityCreeper entity) { + super(server, entity); + } + + public boolean isPowered() { + return getHandle().getPowered(); + } + + public void setPowered(boolean powered) { + CraftServer server = this.server; + Creeper entity = (Creeper) this.getHandle().getBukkitEntity(); + + if (powered) { + CreeperPowerEvent event = new CreeperPowerEvent(entity, CreeperPowerEvent.PowerCause.SET_ON); + server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + getHandle().setPowered(true); + } + } else { + CreeperPowerEvent event = new CreeperPowerEvent(entity, CreeperPowerEvent.PowerCause.SET_OFF); + server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + getHandle().setPowered(false); + } + } + } + + @Override + public void setMaxFuseTicks(int ticks) { + Preconditions.checkArgument(ticks >= 0, "ticks < 0"); + + getHandle().fuseTime = ticks; + } + + @Override + public int getMaxFuseTicks() { + return getHandle().fuseTime; + } + + @Override + public void setExplosionRadius(int radius) { + Preconditions.checkArgument(radius >= 0, "radius < 0"); + + getHandle().explosionRadius = radius; + } + + @Override + public int getExplosionRadius() { + return getHandle().explosionRadius; + } + + @Override + public EntityCreeper getHandle() { + return (EntityCreeper) entity; + } + + @Override + public String toString() { + return "CraftCreeper"; + } + + public EntityType getType() { + return EntityType.CREEPER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomEntity.java new file mode 100644 index 00000000..fdf27e8d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomEntity.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.Entity; +import net.minecraftforge.fml.common.registry.EntityRegistry; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; + +public class CraftCustomEntity extends CraftEntity { + public Class entityClass; + private String entityName; + + public CraftCustomEntity(CraftServer server, Entity entity) { + super(server, entity); + this.entityClass = entity.getClass(); + this.entityName = EntityRegistry.entityTypeMap.get(entityClass); + if (entityName == null) + entityName = entity.getCommandSenderEntity().getName(); + } + + @Override + public Entity getHandle() { + return (Entity) entity; + } + + @Override + public String toString() { + return this.entityName; + } + + public EntityType getType() { + EntityType type = EntityType.fromName(this.entityName); + if (type != null) + return type; + else return EntityType.UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomProjectile.java new file mode 100644 index 00000000..d0f92987 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCustomProjectile.java @@ -0,0 +1,45 @@ +package org.bukkit.craftbukkit.entity; + +import com.mojang.authlib.GameProfile; +import net.minecraft.entity.Entity; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Projectile; +import org.bukkit.projectiles.ProjectileSource; + +import java.util.UUID; + +public class CraftCustomProjectile extends CraftCustomEntity implements Projectile { + private ProjectileSource shooter = null; + private boolean doesBounce; + + public CraftCustomProjectile(CraftServer server, Entity entity) { + super(server, entity); + } + + public static final GameProfile dropper = new GameProfile(UUID.nameUUIDFromBytes("[Dropper]".getBytes()), "[Dropper]"); + + @Override + public ProjectileSource getShooter() { + return shooter; + } + + @Override + public void setShooter(ProjectileSource shooter) { + this.shooter = shooter; + } + + @Override + public boolean doesBounce() { + return doesBounce; + } + + @Override + public void setBounce(boolean doesBounce) { + this.doesBounce = doesBounce; + } + + @Override + public String toString() { + return "CraftCustomProjectile"; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDonkey.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDonkey.java new file mode 100644 index 00000000..c0533bea --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDonkey.java @@ -0,0 +1,29 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityDonkey; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Donkey; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse.Variant; + +public class CraftDonkey extends CraftChestedHorse implements Donkey { + + public CraftDonkey(CraftServer server, EntityDonkey entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftDonkey"; + } + + @Override + public EntityType getType() { + return EntityType.DONKEY; + } + + @Override + public Variant getVariant() { + return Variant.DONKEY; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDragonFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDragonFireball.java new file mode 100644 index 00000000..f8194510 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDragonFireball.java @@ -0,0 +1,17 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityDragonFireball; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.DragonFireball; +import org.bukkit.entity.EntityType; + +public class CraftDragonFireball extends CraftFireball implements DragonFireball { + public CraftDragonFireball(CraftServer server, EntityDragonFireball entity) { + super(server, entity); + } + + @Override + public EntityType getType() { + return EntityType.DRAGON_FIREBALL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java new file mode 100644 index 00000000..7d2b0ad9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityEgg; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Egg; +import org.bukkit.entity.EntityType; + +public class CraftEgg extends CraftProjectile implements Egg { + public CraftEgg(CraftServer server, EntityEgg entity) { + super(server, entity); + } + + @Override + public EntityEgg getHandle() { + return (EntityEgg) entity; + } + + @Override + public String toString() { + return "CraftEgg"; + } + + public EntityType getType() { + return EntityType.EGG; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftElderGuardian.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftElderGuardian.java new file mode 100644 index 00000000..5990d157 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftElderGuardian.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityElderGuardian; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ElderGuardian; +import org.bukkit.entity.EntityType; + +public class CraftElderGuardian extends CraftGuardian implements ElderGuardian { + + public CraftElderGuardian(CraftServer server, EntityElderGuardian entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftElderGuardian"; + } + + @Override + public EntityType getType() { + return EntityType.ELDER_GUARDIAN; + } + + @Override + public boolean isElder() { + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java new file mode 100644 index 00000000..509055b7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java @@ -0,0 +1,55 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityEnderCrystal; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.EntityType; + +public class CraftEnderCrystal extends CraftEntity implements EnderCrystal { + public CraftEnderCrystal(CraftServer server, EntityEnderCrystal entity) { + super(server, entity); + } + + @Override + public boolean isShowingBottom() { + return getHandle().shouldShowBottom(); + } + + @Override + public void setShowingBottom(boolean showing) { + getHandle().setShowBottom(showing); + } + + @Override + public Location getBeamTarget() { + BlockPos pos = getHandle().getBeamTarget(); + return pos == null ? null : new Location(getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public void setBeamTarget(Location location) { + if (location == null) { + getHandle().setBeamTarget(null); + } else if (location.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot set beam target location to different world"); + } else { + getHandle().setBeamTarget(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); + } + } + + @Override + public EntityEnderCrystal getHandle() { + return (EntityEnderCrystal) entity; + } + + @Override + public String toString() { + return "CraftEnderCrystal"; + } + + public EntityType getType() { + return EntityType.ENDER_CRYSTAL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java new file mode 100644 index 00000000..a1c07a53 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java @@ -0,0 +1,62 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; + +import java.util.Set; + +import net.minecraft.entity.MultiPartEntityPart; +import net.minecraft.entity.boss.EntityDragon; +import net.minecraft.entity.boss.dragon.phase.PhaseList; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.ComplexEntityPart; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.EntityType; + +public class CraftEnderDragon extends CraftComplexLivingEntity implements EnderDragon { + public CraftEnderDragon(CraftServer server, EntityDragon entity) { + super(server, entity); + } + + public Set getParts() { + Builder builder = ImmutableSet.builder(); + + for (MultiPartEntityPart part : getHandle().dragonPartArray) { + builder.add((ComplexEntityPart) part.getBukkitEntity()); + } + + return builder.build(); + } + + @Override + public EntityDragon getHandle() { + return (EntityDragon) entity; + } + + @Override + public String toString() { + return "CraftEnderDragon"; + } + + public EntityType getType() { + return EntityType.ENDER_DRAGON; + } + + @Override + public Phase getPhase() { + return Phase.values()[getHandle().getDataManager().get(EntityDragon.PHASE)]; + } + + @Override + public void setPhase(Phase phase) { + getHandle().getPhaseManager().setPhase(getMinecraftPhase(phase)); + } + + public static Phase getBukkitPhase(PhaseList phase) { + return Phase.values()[phase.getId()]; + } + + public static PhaseList getMinecraftPhase(Phase phase) { + return PhaseList.getById(phase.ordinal()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java new file mode 100644 index 00000000..d26b87ec --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java @@ -0,0 +1,56 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.MultiPartEntityPart; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.EnderDragonPart; +import org.bukkit.entity.Entity; + +public class CraftEnderDragonPart extends CraftComplexPart implements EnderDragonPart { + public CraftEnderDragonPart(CraftServer server, MultiPartEntityPart entity) { + super(server, entity); + } + + @Override + public EnderDragon getParent() { + return (EnderDragon) super.getParent(); + } + + @Override + public MultiPartEntityPart getHandle() { + return (MultiPartEntityPart) entity; + } + + @Override + public String toString() { + return "CraftEnderDragonPart"; + } + + public void damage(double amount) { + getParent().damage(amount); + } + + public void damage(double amount, Entity source) { + getParent().damage(amount, source); + } + + public double getHealth() { + return getParent().getHealth(); + } + + public void setHealth(double health) { + getParent().setHealth(health); + } + + public double getMaxHealth() { + return getParent().getMaxHealth(); + } + + public void setMaxHealth(double health) { + getParent().setMaxHealth(health); + } + + public void resetMaxHealth() { + getParent().resetMaxHealth(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java new file mode 100644 index 00000000..1e4885f0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityEnderPearl; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.EntityType; + +public class CraftEnderPearl extends CraftProjectile implements EnderPearl { + public CraftEnderPearl(CraftServer server, EntityEnderPearl entity) { + super(server, entity); + } + + @Override + public EntityEnderPearl getHandle() { + return (EntityEnderPearl) entity; + } + + @Override + public String toString() { + return "CraftEnderPearl"; + } + + public EntityType getType() { + return EntityType.ENDER_PEARL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java new file mode 100644 index 00000000..124a036c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java @@ -0,0 +1,61 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.item.EntityEnderEye; +import net.minecraft.util.math.BlockPos; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EnderSignal; +import org.bukkit.entity.EntityType; + +public class CraftEnderSignal extends CraftEntity implements EnderSignal { + public CraftEnderSignal(CraftServer server, EntityEnderEye entity) { + super(server, entity); + } + + @Override + public EntityEnderEye getHandle() { + return (EntityEnderEye) entity; + } + + @Override + public String toString() { + return "CraftEnderSignal"; + } + + @Override + public EntityType getType() { + return EntityType.ENDER_SIGNAL; + } + + @Override + public Location getTargetLocation() { + return new Location(getWorld(), getHandle().targetX, getHandle().targetY, getHandle().targetZ, getHandle().rotationYaw, getHandle().rotationPitch); + } + + @Override + public void setTargetLocation(Location location) { + Preconditions.checkArgument(getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds"); + getHandle().moveTowards(new BlockPos(location.getX(), location.getY(), location.getZ())); + } + + @Override + public boolean getDropItem() { + return getHandle().shatterOrDrop; + } + + @Override + public void setDropItem(boolean shouldDropItem) { + getHandle().shatterOrDrop = shouldDropItem; + } + + @Override + public int getDespawnTimer() { + return getHandle().despawnTimer; + } + + @Override + public void setDespawnTimer(int time) { + getHandle().despawnTimer = time; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java new file mode 100644 index 00000000..efbd706a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.monster.EntityEnderman; +import org.bukkit.Material; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.Enderman; +import org.bukkit.entity.EntityType; +import org.bukkit.material.MaterialData; + +public class CraftEnderman extends CraftMonster implements Enderman { + public CraftEnderman(CraftServer server, EntityEnderman entity) { + super(server, entity); + } + + public MaterialData getCarriedMaterial() { + IBlockState blockData = getHandle().getHeldBlockState(); + return (blockData == null) ? Material.AIR.getNewData((byte) 0) : CraftMagicNumbers.getMaterial(blockData.getBlock()).getNewData((byte) blockData.getBlock().getMetaFromState(blockData)); + } + + public void setCarriedMaterial(MaterialData data) { + getHandle().setHeldBlockState(CraftMagicNumbers.getBlock(data.getItemTypeId()).getStateFromMeta(data.getData())); + } + + @Override + public EntityEnderman getHandle() { + return (EntityEnderman) entity; + } + + @Override + public String toString() { + return "CraftEnderman"; + } + + public EntityType getType() { + return EntityType.ENDERMAN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java new file mode 100644 index 00000000..eba1f4f5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityEndermite; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Endermite; +import org.bukkit.entity.EntityType; + +public class CraftEndermite extends CraftMonster implements Endermite { + + public CraftEndermite(CraftServer server, EntityEndermite entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftEndermite"; + } + + @Override + public EntityType getType() { + return EntityType.ENDERMITE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java new file mode 100644 index 00000000..bc7a5e6c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -0,0 +1,834 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityAreaEffectCloud; +import net.minecraft.entity.EntityCreature; +import net.minecraft.entity.EntityFlying; +import net.minecraft.entity.EntityHanging; +import net.minecraft.entity.EntityLeashKnot; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.MultiPartEntityPart; +import net.minecraft.entity.boss.EntityDragon; +import net.minecraft.entity.boss.EntityWither; +import net.minecraft.entity.effect.EntityLightningBolt; +import net.minecraft.entity.effect.EntityWeatherEffect; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.item.EntityBoat; +import net.minecraft.entity.item.EntityEnderCrystal; +import net.minecraft.entity.item.EntityEnderEye; +import net.minecraft.entity.item.EntityEnderPearl; +import net.minecraft.entity.item.EntityExpBottle; +import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.entity.item.EntityFireworkRocket; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.entity.item.EntityMinecartChest; +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import net.minecraft.entity.item.EntityMinecartEmpty; +import net.minecraft.entity.item.EntityMinecartFurnace; +import net.minecraft.entity.item.EntityMinecartHopper; +import net.minecraft.entity.item.EntityMinecartMobSpawner; +import net.minecraft.entity.item.EntityMinecartTNT; +import net.minecraft.entity.item.EntityPainting; +import net.minecraft.entity.item.EntityTNTPrimed; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.monster.*; +import net.minecraft.entity.passive.AbstractChestHorse; +import net.minecraft.entity.passive.AbstractHorse; +import net.minecraft.entity.passive.EntityAmbientCreature; +import net.minecraft.entity.passive.EntityAnimal; +import net.minecraft.entity.passive.EntityBat; +import net.minecraft.entity.passive.EntityChicken; +import net.minecraft.entity.passive.EntityCow; +import net.minecraft.entity.passive.EntityDonkey; +import net.minecraft.entity.passive.EntityHorse; +import net.minecraft.entity.passive.EntityLlama; +import net.minecraft.entity.passive.EntityMooshroom; +import net.minecraft.entity.passive.EntityMule; +import net.minecraft.entity.passive.EntityOcelot; +import net.minecraft.entity.passive.EntityParrot; +import net.minecraft.entity.passive.EntityPig; +import net.minecraft.entity.passive.EntityRabbit; +import net.minecraft.entity.passive.EntitySheep; +import net.minecraft.entity.passive.EntitySkeletonHorse; +import net.minecraft.entity.passive.EntitySquid; +import net.minecraft.entity.passive.EntityTameable; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.entity.passive.EntityWaterMob; +import net.minecraft.entity.passive.EntityWolf; +import net.minecraft.entity.passive.EntityZombieHorse; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityDragonFireball; +import net.minecraft.entity.projectile.EntityEgg; +import net.minecraft.entity.projectile.EntityEvokerFangs; +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.entity.projectile.EntityFishHook; +import net.minecraft.entity.projectile.EntityLargeFireball; +import net.minecraft.entity.projectile.EntityLlamaSpit; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.entity.projectile.EntityShulkerBullet; +import net.minecraft.entity.projectile.EntitySmallFireball; +import net.minecraft.entity.projectile.EntitySnowball; +import net.minecraft.entity.projectile.EntitySpectralArrow; +import net.minecraft.entity.projectile.EntityThrowable; +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.entity.projectile.EntityWitherSkull; +import net.minecraft.nbt.NBTTagCompound; + +import net.minecraft.util.DamageSource; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.common.util.FakePlayerFactory; +import org.bukkit.EntityEffect; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.permissions.ServerOperator; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Vector; + +public abstract class CraftEntity implements org.bukkit.entity.Entity { + private static PermissibleBase perm; + + protected final CraftServer server; + protected Entity entity; + private EntityDamageEvent lastDamageEvent; + + public CraftEntity(final CraftServer server, final Entity entity) { + this.server = server; + this.entity = entity; + } + + public static CraftEntity getEntity(CraftServer server, Entity entity) { + /** + * Order is *EXTREMELY* important -- keep it right! =D + */ + if (entity instanceof EntityLivingBase) { + // Players + if (entity instanceof EntityPlayer) { + if (entity instanceof EntityPlayerMP) { return new CraftPlayer(server, (EntityPlayerMP) entity); } + else { + return new CraftPlayer(server, FakePlayerFactory.get(DimensionManager.getWorld(entity.world.provider.getDimension()), ((EntityPlayer) entity).getGameProfile())); + } + } + // Water Animals + else if (entity instanceof EntityWaterMob) { + if (entity instanceof EntitySquid) { return new CraftSquid(server, (EntitySquid) entity); } + else { return new CraftWaterMob(server, (EntityWaterMob) entity); } + } + else if (entity instanceof EntityCreature) { + // Animals + if (entity instanceof EntityAnimal) { + if (entity instanceof EntityChicken) { return new CraftChicken(server, (EntityChicken) entity); } + else if (entity instanceof EntityCow) { + if (entity instanceof EntityMooshroom) { return new CraftMushroomCow(server, (EntityMooshroom) entity); } + else { return new CraftCow(server, (EntityCow) entity); } + } + else if (entity instanceof EntityPig) { return new CraftPig(server, (EntityPig) entity); } + else if (entity instanceof EntityTameable) { + if (entity instanceof EntityWolf) { return new CraftWolf(server, (EntityWolf) entity); } + else if (entity instanceof EntityOcelot) { return new CraftOcelot(server, (EntityOcelot) entity); } + else if (entity instanceof EntityParrot) { return new CraftParrot(server, (EntityParrot) entity); } + else { + return new CraftTameableAnimal(server, (EntityTameable) entity); + } + } + else if (entity instanceof EntitySheep) { return new CraftSheep(server, (EntitySheep) entity); } + else if (entity instanceof AbstractHorse) { + if (entity instanceof AbstractChestHorse){ + if (entity instanceof EntityDonkey) { return new CraftDonkey(server, (EntityDonkey) entity); } + else if (entity instanceof EntityMule) { return new CraftMule(server, (EntityMule) entity); } + else if (entity instanceof EntityLlama) { return new CraftLlama(server, (EntityLlama) entity); } + } else if (entity instanceof EntityHorse) { return new CraftHorse(server, (EntityHorse) entity); } + else if (entity instanceof EntitySkeletonHorse) { return new CraftSkeletonHorse(server, (EntitySkeletonHorse) entity); } + else if (entity instanceof EntityZombieHorse) { return new CraftZombieHorse(server, (EntityZombieHorse) entity); } + } + else if (entity instanceof EntityRabbit) { return new CraftRabbit(server, (EntityRabbit) entity); } + else if (entity instanceof EntityPolarBear) { return new CraftPolarBear(server, (EntityPolarBear) entity); } + else { return new CraftAnimals(server, (EntityAnimal) entity); } + } + // Monsters + else if (entity instanceof EntityMob) { + if (entity instanceof EntityZombie) { + if (entity instanceof EntityPigZombie) { return new CraftPigZombie(server, (EntityPigZombie) entity); } + else if (entity instanceof EntityHusk) { return new CraftHusk(server, (EntityHusk) entity); } + else if (entity instanceof EntityZombieVillager) { return new CraftVillagerZombie(server, (EntityZombieVillager) entity); } + else { return new CraftZombie(server, (EntityZombie) entity); } + } + else if (entity instanceof EntityCreeper) { return new CraftCreeper(server, (EntityCreeper) entity); } + else if (entity instanceof EntityEnderman) { return new CraftEnderman(server, (EntityEnderman) entity); } + else if (entity instanceof EntitySilverfish) { return new CraftSilverfish(server, (EntitySilverfish) entity); } + else if (entity instanceof EntityGiantZombie) { return new CraftGiant(server, (EntityGiantZombie) entity); } + else if (entity instanceof AbstractSkeleton) { + if (entity instanceof EntityStray) { return new CraftStray(server, (EntityStray) entity); } + else if (entity instanceof EntityWitherSkeleton) { return new CraftWitherSkeleton(server, (EntityWitherSkeleton) entity); } + else { return new CraftSkeleton(server, (AbstractSkeleton) entity); } + } + else if (entity instanceof EntityBlaze) { return new CraftBlaze(server, (EntityBlaze) entity); } + else if (entity instanceof EntityWitch) { return new CraftWitch(server, (EntityWitch) entity); } + else if (entity instanceof EntityWither) { return new CraftWither(server, (EntityWither) entity); } + else if (entity instanceof EntitySpider) { + if (entity instanceof EntityCaveSpider) { return new CraftCaveSpider(server, (EntityCaveSpider) entity); } + else { return new CraftSpider(server, (EntitySpider) entity); } + } + else if (entity instanceof EntityEndermite) { return new CraftEndermite(server, (EntityEndermite) entity); } + else if (entity instanceof EntityGuardian) { + if (entity instanceof EntityElderGuardian) { return new CraftElderGuardian(server, (EntityElderGuardian) entity); } + else { return new CraftGuardian(server, (EntityGuardian) entity); } + } + else if (entity instanceof EntityVex) { return new CraftVex(server, (EntityVex) entity); } + else if (entity instanceof AbstractIllager) { + if (entity instanceof EntitySpellcasterIllager) { + if (entity instanceof EntityEvoker) { return new CraftEvoker(server, (EntityEvoker) entity); } + else if (entity instanceof EntityIllusionIllager) { return new CraftIllusioner(server, (EntityIllusionIllager) entity); } + else { return new CraftSpellcaster(server, (EntitySpellcasterIllager) entity); } + } + else if (entity instanceof EntityVindicator) { return new CraftVindicator(server, (EntityVindicator) entity); } + else { return new CraftIllager(server, (AbstractIllager) entity); } + } + + else { return new CraftMonster(server, (EntityMob) entity); } + } + else if (entity instanceof EntityGolem) { + if (entity instanceof EntitySnowman) { return new CraftSnowman(server, (EntitySnowman) entity); } + else if (entity instanceof EntityIronGolem) { return new CraftIronGolem(server, (EntityIronGolem) entity); } + else if (entity instanceof EntityShulker) { return new CraftShulker(server, (EntityShulker) entity); } + else { return new CraftLivingEntity(server, (EntityLivingBase) entity); } + } + else if (entity instanceof EntityVillager) { return new CraftVillager(server, (EntityVillager) entity); } + else { return new CraftCreature(server, (EntityCreature) entity); } + } + // Slimes are a special (and broken) case + else if (entity instanceof EntitySlime) { + if (entity instanceof EntityMagmaCube) { return new CraftMagmaCube(server, (EntityMagmaCube) entity); } + else { return new CraftSlime(server, (EntitySlime) entity); } + } + // Flying + else if (entity instanceof EntityFlying) { + if (entity instanceof EntityGhast) { return new CraftGhast(server, (EntityGhast) entity); } + else { return new CraftFlying(server, (EntityFlying) entity); } + } + else if (entity instanceof EntityDragon) { + return new CraftEnderDragon(server, (EntityDragon) entity); + } + // Ambient + else if (entity instanceof EntityAmbientCreature) { + if (entity instanceof EntityBat) { return new CraftBat(server, (EntityBat) entity); } + else { return new CraftAmbient(server, (EntityAmbientCreature) entity); } + } + else if (entity instanceof EntityArmorStand) { return new CraftArmorStand(server, (EntityArmorStand) entity); } + else { return new CraftLivingEntity(server, (EntityLiving) entity); } + } + else if (entity instanceof MultiPartEntityPart) { + MultiPartEntityPart part = (MultiPartEntityPart) entity; + if (part.parent instanceof EntityDragon) { return new CraftEnderDragonPart(server, (MultiPartEntityPart) entity); } + else { return new CraftComplexPart(server, (MultiPartEntityPart) entity); } + } + else if (entity instanceof EntityXPOrb) { return new CraftExperienceOrb(server, (EntityXPOrb) entity); } + else if (entity instanceof EntityTippedArrow) { + if (((EntityTippedArrow) entity).isTipped()) { return new CraftTippedArrow(server, (EntityTippedArrow) entity); } + else { return new CraftArrow(server, (EntityArrow) entity); } + } + else if (entity instanceof EntitySpectralArrow) { return new CraftSpectralArrow(server, (EntitySpectralArrow) entity); } + else if (entity instanceof EntityArrow) { return new CraftArrow(server, (EntityArrow) entity); } + else if (entity instanceof EntityBoat) { return new CraftBoat(server, (EntityBoat) entity); } + else if (entity instanceof EntityThrowable) { + if (entity instanceof EntityEgg) { return new CraftEgg(server, (EntityEgg) entity); } + else if (entity instanceof EntitySnowball) { return new CraftSnowball(server, (EntitySnowball) entity); } + else if (entity instanceof EntityPotion) { + if (!((EntityPotion) entity).isLingering()) { return new CraftSplashPotion(server, (EntityPotion) entity); } + else { return new CraftLingeringPotion(server, (EntityPotion) entity); } + } + else if (entity instanceof EntityEnderPearl) { return new CraftEnderPearl(server, (EntityEnderPearl) entity); } + else if (entity instanceof EntityExpBottle) { return new CraftThrownExpBottle(server, (EntityExpBottle) entity); } + else { return new CraftProjectile(server, entity); } + } + else if (entity instanceof EntityFallingBlock) { return new CraftFallingBlock(server, (EntityFallingBlock) entity); } + else if (entity instanceof EntityFireball) { + if (entity instanceof EntitySmallFireball) { return new CraftSmallFireball(server, (EntitySmallFireball) entity); } + else if (entity instanceof EntityLargeFireball) { return new CraftLargeFireball(server, (EntityLargeFireball) entity); } + else if (entity instanceof EntityWitherSkull) { return new CraftWitherSkull(server, (EntityWitherSkull) entity); } + else if (entity instanceof EntityDragonFireball) { return new CraftDragonFireball(server, (EntityDragonFireball) entity); } + else { return new CraftFireball(server, (EntityFireball) entity); } + } + else if (entity instanceof EntityEnderEye) { return new CraftEnderSignal(server, (EntityEnderEye) entity); } + else if (entity instanceof EntityEnderCrystal) { return new CraftEnderCrystal(server, (EntityEnderCrystal) entity); } + else if (entity instanceof EntityFishHook) { return new CraftFish(server, (EntityFishHook) entity); } + else if (entity instanceof EntityItem) { return new CraftItem(server, (EntityItem) entity); } + else if (entity instanceof EntityWeatherEffect) { + if (entity instanceof EntityLightningBolt) { return new CraftLightningStrike(server, (EntityLightningBolt) entity); } + else { return new CraftWeather(server, (EntityWeatherEffect) entity); } + } + else if (entity instanceof EntityMinecart) { + if (entity instanceof EntityMinecartFurnace) { return new CraftMinecartFurnace(server, (EntityMinecartFurnace) entity); } + else if (entity instanceof EntityMinecartChest) { return new CraftMinecartChest(server, (EntityMinecartChest) entity); } + else if (entity instanceof EntityMinecartTNT) { return new CraftMinecartTNT(server, (EntityMinecartTNT) entity); } + else if (entity instanceof EntityMinecartHopper) { return new CraftMinecartHopper(server, (EntityMinecartHopper) entity); } + else if (entity instanceof EntityMinecartMobSpawner) { return new CraftMinecartMobSpawner(server, (EntityMinecartMobSpawner) entity); } + else if (entity instanceof EntityMinecartEmpty) { return new CraftMinecartRideable(server, (EntityMinecartEmpty) entity); } + else if (entity instanceof EntityMinecartCommandBlock) { return new CraftMinecartCommand(server, (EntityMinecartCommandBlock) entity); } + else { return new CraftMinecart(server, (EntityMinecart) entity); } + } else if (entity instanceof EntityHanging) { + if (entity instanceof EntityPainting) { return new CraftPainting(server, (EntityPainting) entity); } + else if (entity instanceof EntityItemFrame) { return new CraftItemFrame(server, (EntityItemFrame) entity); } + else if (entity instanceof EntityLeashKnot) { return new CraftLeash(server, (EntityLeashKnot) entity); } + else { return new CraftHanging(server, (EntityHanging) entity); } + } + else if (entity instanceof EntityTNTPrimed) { return new CraftTNTPrimed(server, (EntityTNTPrimed) entity); } + else if (entity instanceof EntityFireworkRocket) { return new CraftFirework(server, (EntityFireworkRocket) entity); } + else if (entity instanceof EntityShulkerBullet) { return new CraftShulkerBullet(server, (EntityShulkerBullet) entity); } + else if (entity instanceof EntityAreaEffectCloud) { return new CraftAreaEffectCloud(server, (EntityAreaEffectCloud) entity); } + else if (entity instanceof EntityEvokerFangs) { return new CraftEvokerFangs(server, (EntityEvokerFangs) entity); } + else if (entity instanceof EntityLlamaSpit) { return new CraftLlamaSpit(server, (EntityLlamaSpit) entity); } + else if (entity != null) { + if (entity instanceof net.minecraft.entity.IProjectile) return new CraftCustomProjectile(server, entity); + return new CraftCustomEntity(server, entity); + } + throw new AssertionError("Unknown entity " + (entity == null ? " is null" : entity.getClass() + ": " + entity)); + } + + public Location getLocation() { + return new Location(getWorld(), entity.posX, entity.posY, entity.posZ, entity.getBukkitYaw(), entity.rotationPitch); + } + + public Location getLocation(Location loc) { + if (loc != null) { + loc.setWorld(getWorld()); + loc.setX(entity.posX); + loc.setY(entity.posY); + loc.setZ(entity.posZ); + loc.setYaw(entity.getBukkitYaw()); + loc.setPitch(entity.rotationPitch); + } + + return loc; + } + + public Vector getVelocity() { + return new Vector(entity.motionX, entity.motionY, entity.motionZ); + } + + public void setVelocity(Vector velocity) { + Preconditions.checkArgument(velocity != null, "velocity"); + velocity.checkFinite(); + entity.motionX = velocity.getX(); + entity.motionY = velocity.getY(); + entity.motionZ = velocity.getZ(); + entity.velocityChanged = true; + } + + @Override + public double getHeight() { + return getHandle().height; + } + + @Override + public double getWidth() { + return getHandle().width; + } + + public boolean isOnGround() { + if (entity instanceof EntityArrow) { + return ((EntityArrow) entity).onGround; + } + return entity.onGround; + } + + public World getWorld() { + return entity.world.getWorld(); + } + + public boolean teleport(Location location) { + return teleport(location, TeleportCause.PLUGIN); + } + + public boolean teleport(Location location, TeleportCause cause) { + Preconditions.checkArgument(location != null, "location"); + location.checkFinite(); + + if (entity.isBeingRidden() || entity.isDead) { + return false; + } + + // If this entity is riding another entity, we must dismount before teleporting. + entity.dismountRidingEntity(); + + entity.world = ((CraftWorld) location.getWorld()).getHandle(); + // entity.setLocation() throws no event, and so cannot be cancelled + entity.setPositionAndRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + // SPIGOT-619: Force sync head rotation also + entity.setRotationYawHead(location.getYaw()); + + return true; + } + + public boolean teleport(org.bukkit.entity.Entity destination) { + return teleport(destination.getLocation()); + } + + public boolean teleport(org.bukkit.entity.Entity destination, TeleportCause cause) { + return teleport(destination.getLocation(), cause); + } + + public List getNearbyEntities(double x, double y, double z) { + List notchEntityList = entity.world.getEntitiesInAABBexcluding(entity, entity.getEntityBoundingBox().grow(x, y, z), null); + List bukkitEntityList = new java.util.ArrayList(notchEntityList.size()); + + for (Entity e : notchEntityList) { + bukkitEntityList.add(e.getBukkitEntity()); + } + return bukkitEntityList; + } + + public int getEntityId() { + return entity.getEntityId(); + } + + public int getFireTicks() { + return entity.fire; + } + + public int getMaxFireTicks() { + return entity.getFireImmuneTicks(); + } + + public void setFireTicks(int ticks) { + entity.fire = ticks; + } + + public void remove() { + entity.setDead(); + } + + public boolean isDead() { + return !entity.isEntityAlive(); + } + + public boolean isValid() { + return entity.isEntityAlive() && entity.valid; + } + + public Server getServer() { + return server; + } + + public Vector getMomentum() { + return getVelocity(); + } + + public void setMomentum(Vector value) { + setVelocity(value); + } + + public org.bukkit.entity.Entity getPassenger() { + return isEmpty() ? null : getHandle().getPassengers().get(0).getBukkitEntity(); + } + + public boolean setPassenger(org.bukkit.entity.Entity passenger) { + Preconditions.checkArgument(!this.equals(passenger), "Entity cannot ride itself."); + if (passenger instanceof CraftEntity) { + eject(); + return ((CraftEntity) passenger).getHandle().startRiding(getHandle()); + } else { + return false; + } + } + + @Override + public List getPassengers() { + return Lists.newArrayList(Lists.transform(getHandle().getPassengers(), new Function() { + @Override + public org.bukkit.entity.Entity apply(Entity input) { + return input.getBukkitEntity(); + } + })); + } + + @Override + public boolean addPassenger(org.bukkit.entity.Entity passenger) { + Preconditions.checkArgument(passenger != null, "passenger == null"); + + return ((CraftEntity) passenger).getHandle().startRiding(getHandle(), true); + } + + @Override + public boolean removePassenger(org.bukkit.entity.Entity passenger) { + Preconditions.checkArgument(passenger != null, "passenger == null"); + + ((CraftEntity) passenger).getHandle().dismountRidingEntity(); + return true; + } + + public boolean isEmpty() { + return !getHandle().isBeingRidden(); + } + + public boolean eject() { + if (isEmpty()) { + return false; + } + + getHandle().removePassengers(); + return true; + } + + public float getFallDistance() { + return getHandle().fallDistance; + } + + public void setFallDistance(float distance) { + getHandle().fallDistance = distance; + } + + public void setLastDamageCause(EntityDamageEvent event) { + lastDamageEvent = event; + } + + public EntityDamageEvent getLastDamageCause() { + return lastDamageEvent; + } + + public UUID getUniqueId() { + return getHandle().getUniqueID(); + } + + public int getTicksLived() { + return getHandle().ticksExisted; + } + + public void setTicksLived(int value) { + if (value <= 0) { + throw new IllegalArgumentException("Age must be at least 1 tick"); + } + getHandle().ticksExisted = value; + } + + public Entity getHandle() { + return entity; + } + + @Override + public void playEffect(EntityEffect type) { + Preconditions.checkArgument(type != null, "type"); + + if (type.getApplicable().isInstance(this)) { + this.getHandle().world.setEntityState(getHandle(), type.getData()); + } + } + + public void setHandle(final Entity entity) { + this.entity = entity; + } + + @Override + public String toString() { + return "CraftEntity{" + "id=" + getEntityId() + '}'; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CraftEntity other = (CraftEntity) obj; + return (this.getEntityId() == other.getEntityId()); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + this.getEntityId(); + return hash; + } + + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + public List getMetadata(String metadataKey) { + return server.getEntityMetadata().getMetadata(this, metadataKey); + } + + public boolean hasMetadata(String metadataKey) { + return server.getEntityMetadata().hasMetadata(this, metadataKey); + } + + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + + public boolean isInsideVehicle() { + return getHandle().isRiding(); + } + + public boolean leaveVehicle() { + if (!isInsideVehicle()) { + return false; + } + + getHandle().dismountRidingEntity(); + return true; + } + + public org.bukkit.entity.Entity getVehicle() { + if (!isInsideVehicle()) { + return null; + } + + return getHandle().getRidingEntity().getBukkitEntity(); // PAIL: rename getVehicle() -> getRootVehicle(), bJ() -> getVehicle() + } + + @Override + public void setCustomName(String name) { + if (name == null) { + name = ""; + } + + getHandle().setCustomNameTag(name); + } + + @Override + public String getCustomName() { + String name = getHandle().getCustomNameTag(); + + if (name == null || name.length() == 0) { + if (getType().getEntityClass() == CraftCustomEntity.class && this instanceof CraftLivingEntity) { + return ((CraftLivingEntity) this).entity.getName(); + } + return null; + } + + return name; + } + + @Override + public void setCustomNameVisible(boolean flag) { + getHandle().setAlwaysRenderNameTag(flag); + } + + @Override + public boolean isCustomNameVisible() { + return getHandle().getAlwaysRenderNameTag(); + } + + @Override + public void sendMessage(String message) { + + } + + @Override + public void sendMessage(String[] messages) { + + } + + @Override + public String getName() { + return getHandle().getName(); + } + + @Override + public boolean isPermissionSet(String name) { + return getPermissibleBase().isPermissionSet(name); + } + + @Override + public boolean isPermissionSet(Permission perm) { + return CraftEntity.getPermissibleBase().isPermissionSet(perm); + } + + @Override + public boolean hasPermission(String name) { + return getPermissibleBase().hasPermission(name); + } + + @Override + public boolean hasPermission(Permission perm) { + return getPermissibleBase().hasPermission(perm); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return getPermissibleBase().addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return getPermissibleBase().addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return getPermissibleBase().addAttachment(plugin, name, value, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return getPermissibleBase().addAttachment(plugin, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + getPermissibleBase().removeAttachment(attachment); + } + + @Override + public void recalculatePermissions() { + getPermissibleBase().recalculatePermissions(); + } + + @Override + public Set getEffectivePermissions() { + return getPermissibleBase().getEffectivePermissions(); + } + + @Override + public boolean isOp() { + return getPermissibleBase().isOp(); + } + + @Override + public void setOp(boolean value) { + getPermissibleBase().setOp(value); + } + + @Override + public void setGlowing(boolean flag) { + getHandle().glowing = flag; + Entity e = getHandle(); + if (e.getFlag(6) != flag) { + e.setFlag(6, flag); + } + } + + @Override + public boolean isGlowing() { + return getHandle().glowing; + } + + @Override + public void setInvulnerable(boolean flag) { + getHandle().setEntityInvulnerable(flag); + } + + @Override + public boolean isInvulnerable() { + return getHandle().isEntityInvulnerable(DamageSource.GENERIC); + } + + @Override + public boolean isSilent() { + return getHandle().isSilent(); + } + + @Override + public void setSilent(boolean flag) { + getHandle().setSilent(flag); + } + + @Override + public boolean hasGravity() { + return !getHandle().hasNoGravity(); + } + + @Override + public void setGravity(boolean gravity) { + getHandle().setNoGravity(!gravity); + } + + @Override + public int getPortalCooldown() { + return getHandle().timeUntilPortal; + } + + @Override + public void setPortalCooldown(int cooldown) { + getHandle().timeUntilPortal = cooldown; + } + + @Override + public Set getScoreboardTags() { + return getHandle().getTags(); + } + + @Override + public boolean addScoreboardTag(String tag) { + return getHandle().addTag(tag); + } + + @Override + public boolean removeScoreboardTag(String tag) { + return getHandle().removeTag(tag); + } + + @Override + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(getHandle().getPushReaction().ordinal()); + } + + protected NBTTagCompound save() { + NBTTagCompound nbttagcompound = new NBTTagCompound(); + + nbttagcompound.setString("id", getHandle().getEntityString()); + getHandle().writeToNBT(nbttagcompound); + + return nbttagcompound; + } + + private static PermissibleBase getPermissibleBase() { + if (perm == null) { + perm = new PermissibleBase(new ServerOperator() { + + @Override + public boolean isOp() { + return false; + } + + @Override + public void setOp(boolean value) { + + } + }); + } + return perm; + } + + // Spigot start + private final Spigot spigot = new Spigot() + { + @Override + public boolean isInvulnerable() + { + return getHandle().isEntityInvulnerable(net.minecraft.util.DamageSource.GENERIC); + } + }; + + @Override + public Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java new file mode 100644 index 00000000..bf61dcb8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityEvoker; +import net.minecraft.entity.monster.EntitySpellcasterIllager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Evoker; + +public class CraftEvoker extends CraftSpellcaster implements Evoker { + + public CraftEvoker(CraftServer server, EntityEvoker entity) { + super(server, entity); + } + + @Override + public EntityEvoker getHandle() { + return (EntityEvoker) super.getHandle(); + } + + @Override + public String toString() { + return "CraftEvoker"; + } + + @Override + public EntityType getType() { + return EntityType.EVOKER; + } + + @Override + public Evoker.Spell getCurrentSpell() { + return Evoker.Spell.values()[getHandle().getSpellType().ordinal()]; + } + + @Override + public void setCurrentSpell(Evoker.Spell spell) { + getHandle().setSpellType(spell == null ? EntitySpellcasterIllager.SpellType.NONE : EntitySpellcasterIllager.SpellType.getFromId(spell.ordinal())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java new file mode 100644 index 00000000..48504fcb --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java @@ -0,0 +1,42 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.projectile.EntityEvokerFangs; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.EvokerFangs; +import org.bukkit.entity.LivingEntity; + +public class CraftEvokerFangs extends CraftEntity implements EvokerFangs { + + public CraftEvokerFangs(CraftServer server, EntityEvokerFangs entity) { + super(server, entity); + } + + @Override + public EntityEvokerFangs getHandle() { + return (EntityEvokerFangs) super.getHandle(); + } + + @Override + public String toString() { + return "CraftEvokerFangs"; + } + + @Override + public EntityType getType() { + return EntityType.EVOKER_FANGS; + } + + @Override + public LivingEntity getOwner() { + EntityLivingBase owner = getHandle().getCaster(); + + return (owner == null) ? null : (LivingEntity) owner.getBukkitEntity(); + } + + @Override + public void setOwner(LivingEntity owner) { + getHandle().setCaster(owner == null ? null : ((CraftLivingEntity) owner).getHandle()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java new file mode 100644 index 00000000..745cbbbe --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java @@ -0,0 +1,34 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityXPOrb; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; + +public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + public CraftExperienceOrb(CraftServer server, EntityXPOrb entity) { + super(server, entity); + } + + public int getExperience() { + return getHandle().xpValue; + } + + public void setExperience(int value) { + getHandle().xpValue = value; + } + + @Override + public EntityXPOrb getHandle() { + return (EntityXPOrb) entity; + } + + @Override + public String toString() { + return "CraftExperienceOrb"; + } + + public EntityType getType() { + return EntityType.EXPERIENCE_ORB; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java new file mode 100644 index 00000000..d6e1b7ea --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java @@ -0,0 +1,67 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityFallingBlock; +import org.bukkit.Material; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; + +public class CraftFallingBlock extends CraftEntity implements FallingBlock { + + public CraftFallingBlock(CraftServer server, EntityFallingBlock entity) { + super(server, entity); + } + + @Override + public EntityFallingBlock getHandle() { + return (EntityFallingBlock) entity; + } + + @Override + public String toString() { + return "CraftFallingBlock"; + } + + public EntityType getType() { + return EntityType.FALLING_BLOCK; + } + + public Material getMaterial() { + return Material.getMaterial(getBlockId()); + } + + public int getBlockId() { + return CraftMagicNumbers.getId(getHandle().getBlock().getBlock()); + } + + public byte getBlockData() { + return (byte) getHandle().getBlock().getBlock().getMetaFromState(getHandle().getBlock()); + } + + public boolean getDropItem() { + return getHandle().shouldDropItem; + } + + public void setDropItem(boolean drop) { + getHandle().shouldDropItem = drop; + } + + @Override + public boolean canHurtEntities() { + return getHandle().hurtEntities; + } + + @Override + public void setHurtEntities(boolean hurtEntities) { + getHandle().hurtEntities = hurtEntities; + } + + @Override + public void setTicksLived(int value) { + super.setTicksLived(value); + + // Second field for EntityFallingBlock + getHandle().ticksExisted = value; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java new file mode 100644 index 00000000..36e8f546 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java @@ -0,0 +1,74 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.util.math.MathHelper; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fireball; +import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.util.Vector; + +public class CraftFireball extends AbstractProjectile implements Fireball { + public CraftFireball(CraftServer server, EntityFireball entity) { + super(server, entity); + } + + public float getYield() { + return getHandle().bukkitYield; + } + + public boolean isIncendiary() { + return getHandle().isIncendiary; + } + + public void setIsIncendiary(boolean isIncendiary) { + getHandle().isIncendiary = isIncendiary; + } + + public void setYield(float yield) { + getHandle().bukkitYield = yield; + } + + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + public void setShooter(ProjectileSource shooter) { + if (shooter instanceof CraftLivingEntity) { + getHandle().shootingEntity = ((CraftLivingEntity) shooter).getHandle(); + } else { + getHandle().shootingEntity = null; + } + getHandle().projectileSource = shooter; + } + + public Vector getDirection() { + return new Vector(getHandle().accelerationX, getHandle().accelerationY, getHandle().accelerationZ); + } + + public void setDirection(Vector direction) { + Validate.notNull(direction, "Direction can not be null"); + double x = direction.getX(); + double y = direction.getY(); + double z = direction.getZ(); + double magnitude = (double) MathHelper.sqrt(x * x + y * y + z * z); + getHandle().accelerationX = x / magnitude; + getHandle().accelerationY = y / magnitude; + getHandle().accelerationZ = z / magnitude; + } + + @Override + public EntityFireball getHandle() { + return (EntityFireball) entity; + } + + @Override + public String toString() { + return "CraftFireball"; + } + + public EntityType getType() { + return EntityType.UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java new file mode 100644 index 00000000..6dbbd218 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java @@ -0,0 +1,72 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityFireworkRocket; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import org.bukkit.Material; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; +import org.bukkit.inventory.meta.FireworkMeta; + +import java.util.Random; + +public class CraftFirework extends CraftEntity implements Firework { + + private final Random random = new Random(); + private final CraftItemStack item; + + public CraftFirework(CraftServer server, EntityFireworkRocket entity) { + super(server, entity); + + ItemStack item = getHandle().getDataManager().get(EntityFireworkRocket.FIREWORK_ITEM); + + if (item.isEmpty()) { + item = new ItemStack(Items.FIREWORKS); + getHandle().getDataManager().set(EntityFireworkRocket.FIREWORK_ITEM, item); + } + + this.item = CraftItemStack.asCraftMirror(item); + + // Ensure the item is a firework... + if (this.item.getType() != Material.FIREWORK) { + this.item.setType(Material.FIREWORK); + } + } + + @Override + public EntityFireworkRocket getHandle() { + return (EntityFireworkRocket) entity; + } + + @Override + public String toString() { + return "CraftFirework"; + } + + @Override + public EntityType getType() { + return EntityType.FIREWORK; + } + + @Override + public FireworkMeta getFireworkMeta() { + return (FireworkMeta) item.getItemMeta(); + } + + @Override + public void setFireworkMeta(FireworkMeta meta) { + item.setItemMeta(meta); + + // Copied from EntityFireworks constructor, update firework lifetime/power + getHandle().lifetime = 10 * (1 + meta.getPower()) + random.nextInt(6) + random.nextInt(7); + + getHandle().getDataManager().setDirty(EntityFireworkRocket.FIREWORK_ITEM); + } + + @Override + public void detonate() { + getHandle().lifetime = 0; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java new file mode 100644 index 00000000..1b3d3ea2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java @@ -0,0 +1,64 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityFishHook; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fish; +import org.bukkit.projectiles.ProjectileSource; + +public class CraftFish extends AbstractProjectile implements Fish { + private double biteChance = -1; + + public CraftFish(CraftServer server, EntityFishHook entity) { + super(server, entity); + } + + public ProjectileSource getShooter() { + if (getHandle().angler != null) { + return getHandle().angler.getBukkitEntity(); + } + + return null; + } + + public void setShooter(ProjectileSource shooter) { + if (shooter instanceof CraftHumanEntity) { + getHandle().angler = (EntityPlayer) ((CraftHumanEntity) shooter).entity; + } + } + + @Override + public EntityFishHook getHandle() { + return (EntityFishHook) entity; + } + + @Override + public String toString() { + return "CraftFish"; + } + + public EntityType getType() { + return EntityType.FISHING_HOOK; + } + + public double getBiteChance() { + EntityFishHook hook = getHandle(); + + if (this.biteChance == -1) { + if (hook.world.isRainingAt(new BlockPos(MathHelper.floor(hook.posX), MathHelper.floor(hook.posY) + 1, MathHelper.floor(hook.posZ)))) { + return 1/300.0; + } + return 1/500.0; + } + return this.biteChance; + } + + public void setBiteChance(double chance) { + Validate.isTrue(chance >= 0 && chance <= 1, "The bite chance must be between 0 and 1."); + this.biteChance = chance; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java new file mode 100644 index 00000000..8ab0ced7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityFlying; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Flying; + +public class CraftFlying extends CraftLivingEntity implements Flying { + + public CraftFlying(CraftServer server, EntityFlying entity) { + super(server, entity); + } + + @Override + public EntityFlying getHandle() { + return (EntityFlying) entity; + } + + @Override + public String toString() { + return "CraftFlying"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java new file mode 100644 index 00000000..23e5e28b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityGhast; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Ghast; + +public class CraftGhast extends CraftFlying implements Ghast { + + public CraftGhast(CraftServer server, EntityGhast entity) { + super(server, entity); + } + + @Override + public EntityGhast getHandle() { + return (EntityGhast) entity; + } + + @Override + public String toString() { + return "CraftGhast"; + } + + public EntityType getType() { + return EntityType.GHAST; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java new file mode 100644 index 00000000..23bc1a9f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityGiantZombie; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Giant; + +public class CraftGiant extends CraftMonster implements Giant { + + public CraftGiant(CraftServer server, EntityGiantZombie entity) { + super(server, entity); + } + + @Override + public EntityGiantZombie getHandle() { + return (EntityGiantZombie) entity; + } + + @Override + public String toString() { + return "CraftGiant"; + } + + public EntityType getType() { + return EntityType.GIANT; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java new file mode 100644 index 00000000..450f54ab --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java @@ -0,0 +1,21 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityGolem; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Golem; + +public class CraftGolem extends CraftCreature implements Golem { + public CraftGolem(CraftServer server, EntityGolem entity) { + super(server, entity); + } + + @Override + public EntityGolem getHandle() { + return (EntityGolem) entity; + } + + @Override + public String toString() { + return "CraftGolem"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java new file mode 100644 index 00000000..21d58fd8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java @@ -0,0 +1,33 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityGuardian; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Guardian; + +public class CraftGuardian extends CraftMonster implements Guardian { + + public CraftGuardian(CraftServer server, EntityGuardian entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftGuardian"; + } + + @Override + public EntityType getType() { + return EntityType.GUARDIAN; + } + + @Override + public boolean isElder() { + return false; + } + + @Override + public void setElder(boolean shouldBeElder) { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java new file mode 100644 index 00000000..42d9b440 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java @@ -0,0 +1,78 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityHanging; +import net.minecraft.util.EnumFacing; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Hanging; + +public class CraftHanging extends CraftEntity implements Hanging { + public CraftHanging(CraftServer server, EntityHanging entity) { + super(server, entity); + } + + public BlockFace getAttachedFace() { + return getFacing().getOppositeFace(); + } + + public void setFacingDirection(BlockFace face) { + setFacingDirection(face, false); + } + + public boolean setFacingDirection(BlockFace face, boolean force) { + EntityHanging hanging = getHandle(); + EnumFacing dir = hanging.facingDirection; + switch (face) { + case SOUTH: + default: + getHandle().updateFacingWithBoundingBox(EnumFacing.SOUTH); + break; + case WEST: + getHandle().updateFacingWithBoundingBox(EnumFacing.WEST); + break; + case NORTH: + getHandle().updateFacingWithBoundingBox(EnumFacing.NORTH); + break; + case EAST: + getHandle().updateFacingWithBoundingBox(EnumFacing.EAST); + break; + } + if (!force && !hanging.onValidSurface()) { + // Revert since it doesn't fit + hanging.updateFacingWithBoundingBox(dir); + return false; + } + return true; + } + + public BlockFace getFacing() { + EnumFacing direction = this.getHandle().facingDirection; + if (direction == null) return BlockFace.SELF; + switch (direction) { + case SOUTH: + default: + return BlockFace.SOUTH; + case WEST: + return BlockFace.WEST; + case NORTH: + return BlockFace.NORTH; + case EAST: + return BlockFace.EAST; + } + } + + @Override + public EntityHanging getHandle() { + return (EntityHanging) entity; + } + + @Override + public String toString() { + return "CraftHanging"; + } + + public EntityType getType() { + return EntityType.UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java new file mode 100644 index 00000000..9ef880e8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java @@ -0,0 +1,73 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityHorse; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventoryHorse; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse; +import org.bukkit.inventory.HorseInventory; + +public class CraftHorse extends CraftAbstractHorse implements Horse { + + public CraftHorse(CraftServer server, EntityHorse entity) { + super(server, entity); + } + + @Override + public EntityHorse getHandle() { + return (EntityHorse) super.getHandle(); + } + + @Override + public Variant getVariant() { + return Variant.HORSE; + } + + @Override + public Color getColor() { + return Color.values()[getHandle().getHorseVariant() & 0xFF]; + } + + @Override + public void setColor(Color color) { + Validate.notNull(color, "Color cannot be null"); + getHandle().setHorseVariant(color.ordinal() & 0xFF | getStyle().ordinal() << 8); + } + + @Override + public Style getStyle() { + return Style.values()[getHandle().getHorseVariant() >>> 8]; + } + + @Override + public void setStyle(Style style) { + Validate.notNull(style, "Style cannot be null"); + getHandle().setHorseVariant(getColor().ordinal() & 0xFF | style.ordinal() << 8); + } + + @Override + public boolean isCarryingChest() { + return false; + } + + @Override + public void setCarryingChest(boolean chest) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public HorseInventory getInventory() { + return new CraftInventoryHorse(getHandle().horseChest); + } + + @Override + public String toString() { + return "CraftHorse{variant=" + getVariant() + ", owner=" + getOwner() + '}'; + } + + @Override + public EntityType getType() { + return EntityType.HORSE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java new file mode 100644 index 00000000..cfde7c1b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -0,0 +1,511 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; + +import java.util.Set; + +import net.minecraft.block.BlockAnvil; +import net.minecraft.block.BlockWorkbench; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.IMerchant; +import net.minecraft.entity.item.EntityMinecartHopper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.IInventory; +import net.minecraft.nbt.NBTTagCompound; + +import net.minecraft.network.play.client.CPacketCloseWindow; +import net.minecraft.network.play.server.SPacketOpenWindow; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityBeacon; +import net.minecraft.tileentity.TileEntityBrewingStand; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.tileentity.TileEntityDropper; +import net.minecraft.tileentity.TileEntityEnchantmentTable; +import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraft.tileentity.TileEntityHopper; +import net.minecraft.tileentity.TileEntityLockable; +import net.minecraft.tileentity.TileEntityShulkerBox; +import net.minecraft.util.CooldownTracker; +import net.minecraft.util.EnumHandSide; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentString; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.Merchant; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Villager; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.craftbukkit.inventory.CraftContainer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.inventory.CraftMerchant; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + private CraftInventoryPlayer inventory; + private final CraftInventory enderChest; + protected final PermissibleBase perm = new PermissibleBase(this); + private boolean op; + private GameMode mode; + + public CraftHumanEntity(final CraftServer server, final EntityPlayer entity) { + super(server, entity); + mode = server.getDefaultGameMode(); + this.inventory = new CraftInventoryPlayer(entity.inventory); + enderChest = new CraftInventory(entity.getInventoryEnderChest()); + } + + public String getName() { + return getHandle().getName(); + } + + public PlayerInventory getInventory() { + return inventory; + } + + public EntityEquipment getEquipment() { + return inventory; + } + + public Inventory getEnderChest() { + return enderChest; + } + + public MainHand getMainHand() { + return getHandle().getPrimaryHand()== EnumHandSide.LEFT ? MainHand.LEFT : MainHand.RIGHT; + } + + public ItemStack getItemInHand() { + return getInventory().getItemInHand(); + } + + public void setItemInHand(ItemStack item) { + getInventory().setItemInHand(item); + } + + public ItemStack getItemOnCursor() { + return CraftItemStack.asCraftMirror(getHandle().inventory.getItemStack()); + } + + public void setItemOnCursor(ItemStack item) { + net.minecraft.item.ItemStack stack = CraftItemStack.asNMSCopy(item); + getHandle().inventory.setItemStack(stack); + if (this instanceof CraftPlayer) { + ((EntityPlayerMP) getHandle()).updateHeldItem(); // Send set slot for cursor + } + } + + public boolean isSleeping() { + return getHandle().sleeping; + } + + public int getSleepTicks() { + return getHandle().sleepTimer; + } + + public boolean isOp() { + return op; + } + + public boolean isPermissionSet(String name) { + return perm.isPermissionSet(name); + } + + public boolean isPermissionSet(Permission perm) { + return this.perm.isPermissionSet(perm); + } + + public boolean hasPermission(String name) { + return perm.hasPermission(name); + } + + public boolean hasPermission(Permission perm) { + return this.perm.hasPermission(perm); + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return perm.addAttachment(plugin, name, value); + } + + public PermissionAttachment addAttachment(Plugin plugin) { + return perm.addAttachment(plugin); + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return perm.addAttachment(plugin, name, value, ticks); + } + + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return perm.addAttachment(plugin, ticks); + } + + public void removeAttachment(PermissionAttachment attachment) { + perm.removeAttachment(attachment); + } + + public void recalculatePermissions() { + perm.recalculatePermissions(); + } + + public void setOp(boolean value) { + this.op = value; + perm.recalculatePermissions(); + } + + public Set getEffectivePermissions() { + return perm.getEffectivePermissions(); + } + + public GameMode getGameMode() { + return mode; + } + + public void setGameMode(GameMode mode) { + if (mode == null) { + throw new IllegalArgumentException("Mode cannot be null"); + } + + this.mode = mode; + } + + @Override + public EntityPlayer getHandle() { + return (EntityPlayer) entity; + } + + public void setHandle(final EntityPlayer entity) { + super.setHandle(entity); + this.inventory = new CraftInventoryPlayer(entity.inventory); + } + + @Override + public String toString() { + return "CraftHumanEntity{" + "id=" + getEntityId() + "name=" + getName() + '}'; + } + + public InventoryView getOpenInventory() { + return getHandle().inventoryContainer.getBukkitView(); + } + + public InventoryView openInventory(Inventory inventory) { + if(!(getHandle() instanceof EntityPlayerMP)) return null; + EntityPlayerMP player = (EntityPlayerMP) getHandle(); + InventoryType type = inventory.getType(); + Container formerContainer = getHandle().inventoryContainer; + + IInventory iinventory = (inventory instanceof CraftInventory) ? ((CraftInventory) inventory).getInventory() : new org.bukkit.craftbukkit.inventory.InventoryWrapper(inventory); + + switch (type) { + case PLAYER: + case CHEST: + case ENDER_CHEST: + getHandle().displayGUIChest(iinventory); + break; + case DISPENSER: + if (iinventory instanceof TileEntityDispenser) { + getHandle().displayGUIChest((TileEntityDispenser) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:dispenser"); + } + break; + case DROPPER: + if (iinventory instanceof TileEntityDropper) { + getHandle().displayGUIChest((TileEntityDropper) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:dropper"); + } + break; + case FURNACE: + if (iinventory instanceof TileEntityFurnace) { + getHandle().displayGUIChest((TileEntityFurnace) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:furnace"); + } + break; + case WORKBENCH: + openCustomInventory(inventory, player, "minecraft:crafting_table"); + break; + case BREWING: + if (iinventory instanceof TileEntityBrewingStand) { + getHandle().displayGUIChest((TileEntityBrewingStand) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:brewing_stand"); + } + break; + case ENCHANTING: + openCustomInventory(inventory, player, "minecraft:enchanting_table"); + break; + case HOPPER: + if (iinventory instanceof TileEntityHopper) { + getHandle().displayGUIChest((TileEntityHopper) iinventory); + } else if (iinventory instanceof EntityMinecartHopper) { + getHandle().displayGUIChest((EntityMinecartHopper) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:hopper"); + } + break; + case BEACON: + if (iinventory instanceof TileEntityBeacon) { + getHandle().displayGUIChest((TileEntityBeacon) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:beacon"); + } + break; + case ANVIL: + if (iinventory instanceof BlockAnvil.Anvil) { + getHandle().displayGui((BlockAnvil.Anvil) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:anvil"); + } + break; + case SHULKER_BOX: + if (iinventory instanceof TileEntityShulkerBox) { + getHandle().displayGUIChest((TileEntityShulkerBox) iinventory); + } else { + openCustomInventory(inventory, player, "minecraft:shulker_box"); + } + break; + case CREATIVE: + case CRAFTING: + throw new IllegalArgumentException("Can't open a " + type + " inventory!"); + } + if (getHandle().inventoryContainer == formerContainer) { + return null; + } + getHandle().inventoryContainer.checkReachable = false; + return getHandle().inventoryContainer.getBukkitView(); + } + + private void openCustomInventory(Inventory inventory, EntityPlayerMP player, String windowType) { + if (player.connection == null) return; + Container container = new CraftContainer(inventory, this.getHandle(), player.getNextWindowIdCB()); + + container = CraftEventFactory.callInventoryOpenEvent(player, container); + if(container == null) return; + + String title = container.getBukkitView().getTitle(); + int size = container.getBukkitView().getTopInventory().getSize(); + + // Special cases + if (windowType.equals("minecraft:crafting_table") + || windowType.equals("minecraft:anvil") + || windowType.equals("minecraft:enchanting_table") + ) { + size = 0; + } + + player.connection.sendPacket(new SPacketOpenWindow(container.windowId, windowType, new TextComponentString(title), size)); + getHandle().inventoryContainer = container; + getHandle().inventoryContainer.addListener(player); + } + + public InventoryView openWorkbench(Location location, boolean force) { + if (!force) { + Block block = location.getBlock(); + if (block.getType() != Material.WORKBENCH) { + return null; + } + } + if (location == null) { + location = getLocation(); + } + getHandle().displayGui(new BlockWorkbench.InterfaceCraftingTable(getHandle().world, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); + if (force) { + getHandle().inventoryContainer.checkReachable = false; + } + return getHandle().inventoryContainer.getBukkitView(); + } + + public InventoryView openEnchanting(Location location, boolean force) { + if (!force) { + Block block = location.getBlock(); + if (block.getType() != Material.ENCHANTMENT_TABLE) { + return null; + } + } + if (location == null) { + location = getLocation(); + } + + // If there isn't an enchant table we can force create one, won't be very useful though. + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + TileEntity container = getHandle().world.getTileEntity(pos); + if (container == null && force) { + container = new TileEntityEnchantmentTable(); + container.setWorld(getHandle().world); + container.setPos(pos); + } + getHandle().displayGui((TileEntityLockable) container); + + if (force) { + getHandle().inventoryContainer.checkReachable = false; + } + return getHandle().inventoryContainer.getBukkitView(); + } + + public void openInventory(InventoryView inventory) { + if (!(getHandle() instanceof EntityPlayerMP)) return; // TODO: NPC support? + if (((EntityPlayerMP) getHandle()).connection == null) return; + if (getHandle().inventoryContainer != getHandle().inventoryContainer) { + // fire INVENTORY_CLOSE if one already open + ((EntityPlayerMP)getHandle()).connection.processCloseWindow(new CPacketCloseWindow(getHandle().inventoryContainer.windowId)); + } + EntityPlayerMP player = (EntityPlayerMP) getHandle(); + Container container; + if (inventory instanceof CraftInventoryView) { + container = ((CraftInventoryView) inventory).getHandle(); + } else { + container = new CraftContainer(inventory, this.getHandle(), player.getNextWindowIdCB()); + } + + // Trigger an INVENTORY_OPEN event + container = CraftEventFactory.callInventoryOpenEvent(player, container); + if (container == null) { + return; + } + + // Now open the window + InventoryType type = inventory.getType(); + String windowType = CraftContainer.getNotchInventoryType(type); + String title = inventory.getTitle(); + int size = inventory.getTopInventory().getSize(); + player.connection.sendPacket(new SPacketOpenWindow(container.windowId, windowType, new TextComponentString(title), size)); + player.inventoryContainer = container; + player.inventoryContainer.addListener(player); + } + + @Override + public InventoryView openMerchant(Villager villager, boolean force) { + Preconditions.checkNotNull(villager, "villager cannot be null"); + + return this.openMerchant((Merchant) villager, force); + } + + @Override + public InventoryView openMerchant(Merchant merchant, boolean force) { + Preconditions.checkNotNull(merchant, "merchant cannot be null"); + + if (!force && merchant.isTrading()) { + return null; + } else if (merchant.isTrading()) { + // we're not supposed to have multiple people using the same merchant, so we have to close it. + merchant.getTrader().closeInventory(); + } + + IMerchant mcMerchant; + if (merchant instanceof CraftVillager) { + mcMerchant = ((CraftVillager) merchant).getHandle(); + } else if (merchant instanceof CraftMerchant) { + mcMerchant = ((CraftMerchant) merchant).getMerchant(); + } else { + throw new IllegalArgumentException("Can't open merchant " + merchant.toString()); + } + + mcMerchant.setCustomer(this.getHandle()); + this.getHandle().displayVillagerTradeGui(mcMerchant); + + return this.getHandle().inventoryContainer.getBukkitView(); + } + + public void closeInventory() { + getHandle().closeScreen(); + } + + public boolean isBlocking() { + return getHandle().isActiveItemStackBlocking(); + } + + @Override + public boolean isHandRaised() { + return getHandle().isHandActive(); + } + + public boolean setWindowProperty(InventoryView.Property prop, int value) { + return false; + } + + public int getExpToLevel() { + return getHandle().xpBarCap(); + } + + @Override + public boolean hasCooldown(Material material) { + Preconditions.checkArgument(material != null, "material"); + + return getHandle().getCooldownTracker().hasCooldown(CraftMagicNumbers.getItem(material)); + } + + @Override + public int getCooldown(Material material) { + Preconditions.checkArgument(material != null, "material"); + + CooldownTracker.Cooldown cooldown = getHandle().getCooldownTracker().cooldowns.get(CraftMagicNumbers.getItem(material)); + return (cooldown == null) ? 0 : Math.max(0, cooldown.expireTicks - getHandle().getCooldownTracker().ticks); + } + + @Override + public void setCooldown(Material material, int ticks) { + Preconditions.checkArgument(material != null, "material"); + Preconditions.checkArgument(ticks >= 0, "Cannot have negative cooldown"); + + getHandle().getCooldownTracker().setCooldown(CraftMagicNumbers.getItem(material), ticks); + } + + @Override + public org.bukkit.entity.Entity getShoulderEntityLeft() { + if (!getHandle().getLeftShoulderEntity().hasNoTags()) { + Entity shoulder = EntityList.createEntityFromNBT(getHandle().getLeftShoulderEntity(), getHandle().world); + + return (shoulder == null) ? null : shoulder.getBukkitEntity(); + } + + return null; + } + + @Override + public void setShoulderEntityLeft(org.bukkit.entity.Entity entity) { + getHandle().setLeftShoulderEntity(entity == null ? new NBTTagCompound() : ((CraftEntity) entity).save()); + if (entity != null) { + entity.remove(); + } + } + + @Override + public org.bukkit.entity.Entity getShoulderEntityRight() { + if (!getHandle().getRightShoulderEntity().hasNoTags()) { + Entity shoulder = EntityList.createEntityFromNBT(getHandle().getRightShoulderEntity(), getHandle().world); + + return (shoulder == null) ? null : shoulder.getBukkitEntity(); + } + + return null; + } + + @Override + public void setShoulderEntityRight(org.bukkit.entity.Entity entity) { + getHandle().setRightShoulderEntity(entity == null ? new NBTTagCompound() : ((CraftEntity) entity).save()); + if (entity != null) { + entity.remove(); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHusk.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHusk.java new file mode 100644 index 00000000..f8aa4f29 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHusk.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityHusk; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Husk; + +public class CraftHusk extends CraftZombie implements Husk { + + public CraftHusk(CraftServer server, EntityHusk entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftHusk"; + } + + @Override + public EntityType getType() { + return EntityType.HUSK; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java new file mode 100644 index 00000000..4bd01d87 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.AbstractIllager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Illager; + +public class CraftIllager extends CraftMonster implements Illager { + + public CraftIllager(CraftServer server, AbstractIllager entity) { + super(server, entity); + } + + @Override + public AbstractIllager getHandle() { + return (AbstractIllager) super.getHandle(); + } + + @Override + public String toString() { + return "CraftIllager"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java new file mode 100644 index 00000000..7d3b17c8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityIllusionIllager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Illusioner; + +public class CraftIllusioner extends CraftSpellcaster implements Illusioner { + + public CraftIllusioner(CraftServer server, EntityIllusionIllager entity) { + super(server, entity); + } + + @Override + public EntityIllusionIllager getHandle() { + return (EntityIllusionIllager) super.getHandle(); + } + + @Override + public String toString() { + return "CraftIllusioner"; + } + + @Override + public EntityType getType() { + return EntityType.ILLUSIONER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java new file mode 100644 index 00000000..65c353a0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java @@ -0,0 +1,35 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityIronGolem; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.IronGolem; + +public class CraftIronGolem extends CraftGolem implements IronGolem { + public CraftIronGolem(CraftServer server, EntityIronGolem entity) { + super(server, entity); + } + + @Override + public EntityIronGolem getHandle() { + return (EntityIronGolem) entity; + } + + @Override + public String toString() { + return "CraftIronGolem"; + } + + public boolean isPlayerCreated() { + return getHandle().isPlayerCreated(); + } + + public void setPlayerCreated(boolean playerCreated) { + getHandle().setPlayerCreated(playerCreated); + } + + @Override + public EntityType getType() { + return EntityType.IRON_GOLEM; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java new file mode 100644 index 00000000..d0d28627 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -0,0 +1,47 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Item; +import org.bukkit.inventory.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.CraftServer; + +public class CraftItem extends CraftEntity implements Item { + private final EntityItem item; + + public CraftItem(CraftServer server, Entity entity, EntityItem item) { + super(server, entity); + this.item = item; + } + + public CraftItem(CraftServer server, EntityItem entity) { + this(server, entity, entity); + } + + public ItemStack getItemStack() { + return CraftItemStack.asCraftMirror(item.getItem()); + } + + public void setItemStack(ItemStack stack) { + item.setItem(CraftItemStack.asNMSCopy(stack)); + } + + public int getPickupDelay() { + return item.pickupDelay; + } + + public void setPickupDelay(int delay) { + item.pickupDelay = Math.min(delay, Short.MAX_VALUE); + } + + @Override + public String toString() { + return "CraftItem"; + } + + public EntityType getType() { + return EntityType.DROPPED_ITEM; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java new file mode 100644 index 00000000..226fd621 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java @@ -0,0 +1,127 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import org.apache.commons.lang3.Validate; + +import org.bukkit.Rotation; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemFrame; + +public class CraftItemFrame extends CraftHanging implements ItemFrame { + public CraftItemFrame(CraftServer server, EntityItemFrame entity) { + super(server, entity); + } + + public boolean setFacingDirection(BlockFace face, boolean force) { + if (!super.setFacingDirection(face, force)) { + return false; + } + + update(); + + return true; + } + + private void update() { + EntityItemFrame old = this.getHandle(); + + WorldServer world = ((CraftWorld) getWorld()).getHandle(); + BlockPos position = old.getPosition(); + EnumFacing direction = old.getHorizontalFacing(); + ItemStack item = old.getDisplayedItem() != null ? old.getDisplayedItem().copy() : null; + + old.setDead(); + + EntityItemFrame frame = new EntityItemFrame(world,position,direction); + frame.setDisplayedItem(item); + world.spawnEntity(frame); + this.entity = frame; + } + + public void setItem(org.bukkit.inventory.ItemStack item) { + getHandle().setDisplayedItem(CraftItemStack.asNMSCopy(item)); + } + + public org.bukkit.inventory.ItemStack getItem() { + return CraftItemStack.asBukkitCopy(getHandle().getDisplayedItem()); + } + + public Rotation getRotation() { + return toBukkitRotation(getHandle().getRotation()); + } + + Rotation toBukkitRotation(int value) { + // Translate NMS rotation integer to Bukkit API + switch (value) { + case 0: + return Rotation.NONE; + case 1: + return Rotation.CLOCKWISE_45; + case 2: + return Rotation.CLOCKWISE; + case 3: + return Rotation.CLOCKWISE_135; + case 4: + return Rotation.FLIPPED; + case 5: + return Rotation.FLIPPED_45; + case 6: + return Rotation.COUNTER_CLOCKWISE; + case 7: + return Rotation.COUNTER_CLOCKWISE_45; + default: + throw new AssertionError("Unknown rotation " + value + " for " + getHandle()); + } + } + + public void setRotation(Rotation rotation) { + Validate.notNull(rotation, "Rotation cannot be null"); + getHandle().setItemRotation(toInteger(rotation)); + } + + static int toInteger(Rotation rotation) { + // Translate Bukkit API rotation to NMS integer + switch (rotation) { + case NONE: + return 0; + case CLOCKWISE_45: + return 1; + case CLOCKWISE: + return 2; + case CLOCKWISE_135: + return 3; + case FLIPPED: + return 4; + case FLIPPED_45: + return 5; + case COUNTER_CLOCKWISE: + return 6; + case COUNTER_CLOCKWISE_45: + return 7; + default: + throw new IllegalArgumentException(rotation + " is not applicable to an ItemFrame"); + } + } + + @Override + public EntityItemFrame getHandle() { + return (EntityItemFrame) entity; + } + + @Override + public String toString() { + return "CraftItemFrame{item=" + getItem() + ", rotation=" + getRotation() + "}"; + } + + public EntityType getType() { + return EntityType.ITEM_FRAME; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java new file mode 100644 index 00000000..8bec7381 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityLargeFireball; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LargeFireball; + +public class CraftLargeFireball extends CraftFireball implements LargeFireball { + public CraftLargeFireball(CraftServer server, EntityLargeFireball entity) { + super(server, entity); + } + + @Override + public void setYield(float yield) { + super.setYield(yield); + getHandle().explosionPower = (int) yield; + } + + @Override + public EntityLargeFireball getHandle() { + return (EntityLargeFireball) entity; + } + + @Override + public String toString() { + return "CraftLargeFireball"; + } + + public EntityType getType() { + return EntityType.FIREBALL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java new file mode 100644 index 00000000..dcc58e53 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityLeashKnot; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LeashHitch; + +public class CraftLeash extends CraftHanging implements LeashHitch { + public CraftLeash(CraftServer server, EntityLeashKnot entity) { + super(server, entity); + } + + @Override + public EntityLeashKnot getHandle() { + return (EntityLeashKnot) entity; + } + + @Override + public String toString() { + return "CraftLeash"; + } + + public EntityType getType() { + return EntityType.LEASH_HITCH; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java new file mode 100644 index 00000000..57a520f3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.effect.EntityLightningBolt; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LightningStrike; + +public class CraftLightningStrike extends CraftEntity implements LightningStrike { + public CraftLightningStrike(final CraftServer server, final EntityLightningBolt entity) { + super(server, entity); + } + + public boolean isEffect() { + return ((EntityLightningBolt) super.getHandle()).isEffect; + } + + @Override + public EntityLightningBolt getHandle() { + return (EntityLightningBolt) entity; + } + + @Override + public String toString() { + return "CraftLightningStrike"; + } + + public EntityType getType() { + return EntityType.LIGHTNING; + } + + // Spigot start + private final LightningStrike.Spigot spigot = new LightningStrike.Spigot() { + + @Override + public boolean isSilent() + { + return getHandle().isSilent; + } + }; + + @Override + public LightningStrike.Spigot spigot() { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLingeringPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLingeringPotion.java new file mode 100644 index 00000000..93b7aa48 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLingeringPotion.java @@ -0,0 +1,42 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityPotion; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LingeringPotion; +import org.bukkit.inventory.ItemStack; + +public class CraftLingeringPotion extends CraftThrownPotion implements LingeringPotion { + + public CraftLingeringPotion(CraftServer server, EntityPotion entity) { + super(server, entity); + } + + public void setItem(ItemStack item) { + // The ItemStack must not be null. + Validate.notNull(item, "ItemStack cannot be null."); + + // The ItemStack must be a potion. + Validate.isTrue(item.getType() == Material.LINGERING_POTION, "ItemStack must be a lingering potion. This item stack was " + item.getType() + "."); + + getHandle().setItem(CraftItemStack.asNMSCopy(item)); + } + + @Override + public EntityPotion getHandle() { + return (EntityPotion) entity; + } + + @Override + public String toString() { + return "CraftLingeringPotion"; + } + + @Override + public EntityType getType() { + return EntityType.LINGERING_POTION; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java new file mode 100644 index 00000000..a257fa81 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -0,0 +1,505 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.boss.EntityWither; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.item.EntityEnderPearl; +import net.minecraft.entity.item.EntityExpBottle; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityDragonFireball; +import net.minecraft.entity.projectile.EntityEgg; +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.entity.projectile.EntityFishHook; +import net.minecraft.entity.projectile.EntityLargeFireball; +import net.minecraft.entity.projectile.EntityLlamaSpit; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.entity.projectile.EntityShulkerBullet; +import net.minecraft.entity.projectile.EntitySmallFireball; +import net.minecraft.entity.projectile.EntitySnowball; +import net.minecraft.entity.projectile.EntitySpectralArrow; +import net.minecraft.entity.projectile.EntityThrowable; +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.entity.projectile.EntityWitherSkull; +import net.minecraft.potion.Potion; +import net.minecraft.util.DamageSource; +import net.minecraftforge.fml.common.registry.EntityRegistry; +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftEntityEquipment; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.DragonFireball; +import org.bukkit.entity.Egg; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.Fish; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LingeringPotion; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.LlamaSpit; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.ShulkerBullet; +import org.bukkit.entity.SmallFireball; +import org.bukkit.entity.Snowball; +import org.bukkit.entity.SpectralArrow; +import org.bukkit.entity.ThrownExpBottle; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.TippedArrow; +import org.bukkit.entity.WitherSkull; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.bukkit.util.BlockIterator; +import org.bukkit.util.Vector; + +public class CraftLivingEntity extends CraftEntity implements LivingEntity { + private CraftEntityEquipment equipment; + public String entityName; + + public CraftLivingEntity(final CraftServer server, final EntityLivingBase entity) { + super(server, entity); + + if (entity instanceof EntityLiving || entity instanceof EntityArmorStand) { + equipment = new CraftEntityEquipment(this); + } + this.entityName = EntityRegistry.entityTypeMap.get(entity.getClass()); + if (entityName == null) { + entityName = entity.getName(); + } + } + + @Override + public double getHealth() { + return Math.min(Math.max(0, getHandle().getHealth()), getMaxHealth()); + } + + @Override + public void setHealth(double health) { + health = (float) health; + if ((health < 0) || (health > getMaxHealth())) { + // Paper - Be more informative + throw new IllegalArgumentException("Health must be between 0 and " + getMaxHealth() + ", but was " + health + + ". (attribute base value: " + this.getHandle().getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).getAttributeValue() + + (this instanceof CraftPlayer ? ", player: " + this.getName() + ')' : ')')); + } + // Cauldron start - setHealth must be set before onDeath to respect events that may prevent death. + getHandle().setHealth((float) health); + + if (entity instanceof EntityPlayerMP && health == 0) { + ((EntityPlayerMP) entity).onDeath(DamageSource.GENERIC); + } + // Cauldron end + } + + public double getMaxHealth() { + return getHandle().getMaxHealth(); + } + + public void setMaxHealth(double amount) { + Validate.isTrue(amount > 0, "Max health must be greater than 0"); + + getHandle().getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(amount); + + if (getHealth() > amount) { + setHealth(amount); + } + } + + public void resetMaxHealth() { + setMaxHealth(getHandle().getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).getAttribute().getDefaultValue()); + } + + public double getEyeHeight() { + return getHandle().getEyeHeight(); + } + + public double getEyeHeight(boolean ignorePose) { + return getEyeHeight(); + } + + private List getLineOfSight(Set transparent, int maxDistance, int maxLength) { + if (maxDistance > 120) { + maxDistance = 120; + } + ArrayList blocks = new ArrayList(); + Iterator itr = new BlockIterator(this, maxDistance); + while (itr.hasNext()) { + Block block = itr.next(); + blocks.add(block); + if (maxLength != 0 && blocks.size() > maxLength) { + blocks.remove(0); + } + Material material = block.getType(); + if (transparent == null) { + if (!material.equals(Material.AIR)) { + break; + } + } else { + if (!transparent.contains(material)) { + break; + } + } + } + return blocks; + } + + public List getLineOfSight(Set transparent, int maxDistance) { + return getLineOfSight(transparent, maxDistance, 0); + } + + public Block getTargetBlock(Set transparent, int maxDistance) { + List blocks = getLineOfSight(transparent, maxDistance, 1); + return blocks.get(0); + } + + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return getLineOfSight(transparent, maxDistance, 2); + } + + public int getRemainingAir() { + return getHandle().getAir(); + } + + public void setRemainingAir(int ticks) { + getHandle().setAir(ticks); + } + + public int getMaximumAir() { + return getHandle().maxAirTicks; + } + + public void setMaximumAir(int ticks) { + getHandle().maxAirTicks = ticks; + } + + public void damage(double amount) { + damage(amount, null); + } + + public void damage(double amount, Entity source) { + DamageSource reason = DamageSource.GENERIC; + + if (source instanceof HumanEntity) { + reason = DamageSource.causePlayerDamage(((CraftHumanEntity) source).getHandle()); + } else if (source instanceof LivingEntity) { + reason = DamageSource.causeMobDamage(((CraftLivingEntity) source).getHandle()); + } + + entity.attackEntityFrom(reason, (float) amount); + } + + public Location getEyeLocation() { + Location loc = getLocation(); + loc.setY(loc.getY() + getEyeHeight()); + return loc; + } + + public int getMaximumNoDamageTicks() { + return getHandle().maxHurtResistantTime; + } + + public void setMaximumNoDamageTicks(int ticks) { + getHandle().maxHurtResistantTime = ticks; + } + + public double getLastDamage() { + return getHandle().lastDamage; + } + + public void setLastDamage(double damage) { + getHandle().lastDamage = (float) damage; + } + + public int getNoDamageTicks() { + return getHandle().hurtResistantTime; + } + + public void setNoDamageTicks(int ticks) { + getHandle().hurtResistantTime = ticks; + } + + @Override + public EntityLivingBase getHandle() { + return (EntityLivingBase) entity; + } + + public void setHandle(final EntityLiving entity) { + super.setHandle(entity); + } + + @Override + public String toString() { + return "CraftLivingEntity{" + "id=" + getEntityId() + ", name=" + this.entityName + "}"; + } + + public Player getKiller() { + return getHandle().attackingPlayer == null ? null : (Player) getHandle().attackingPlayer.getBukkitEntity(); + } + + public boolean addPotionEffect(PotionEffect effect) { + return addPotionEffect(effect, false); + } + + public boolean addPotionEffect(PotionEffect effect, boolean force) { + if (hasPotionEffect(effect.getType())) { + if (!force) { + return false; + } + removePotionEffect(effect.getType()); + } + getHandle().addPotionEffect(new net.minecraft.potion.PotionEffect(Potion.getPotionById(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles())); + return true; + } + + public boolean addPotionEffects(Collection effects) { + boolean success = true; + for (PotionEffect effect : effects) { + success &= addPotionEffect(effect); + } + return success; + } + + public boolean hasPotionEffect(PotionEffectType type) { + return getHandle().isPotionActive(Potion.getPotionById(type.getId())); + } + + @Override + public PotionEffect getPotionEffect(PotionEffectType type) { + net.minecraft.potion.PotionEffect handle = getHandle().getActivePotionEffect(Potion.getPotionById(type.getId())); + return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(Potion.getIdFromPotion(handle.getPotion())), handle.getDuration(), handle.getAmplifier(), handle.getIsAmbient(), handle.doesShowParticles()); + } + + public void removePotionEffect(PotionEffectType type) { + getHandle().removePotionEffect(Potion.getPotionById(type.getId())); + } + + public Collection getActivePotionEffects() { + List effects = new ArrayList(); + for (net.minecraft.potion.PotionEffect handle : getHandle().getActivePotionMap().values()) { + effects.add(new PotionEffect(PotionEffectType.getById(Potion.getIdFromPotion(handle.getPotion())), handle.getDuration(), handle.getAmplifier(), handle.getIsAmbient(), handle.doesShowParticles())); + } + return effects; + } + + public T launchProjectile(Class projectile) { + return launchProjectile(projectile, null); + } + + @SuppressWarnings("unchecked") + public T launchProjectile(Class projectile, Vector velocity) { + net.minecraft.world.World world = ((CraftWorld) getWorld()).getHandle(); + net.minecraft.entity.Entity launch = null; + + if (Snowball.class.isAssignableFrom(projectile)) { + launch = new EntitySnowball(world, getHandle()); + ((EntityThrowable) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, 0.0F, 1.5F, 1.0F); // ItemSnowball + } else if (Egg.class.isAssignableFrom(projectile)) { + launch = new EntityEgg(world, getHandle()); + ((EntityThrowable) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, 0.0F, 1.5F, 1.0F); // ItemEgg + } else if (EnderPearl.class.isAssignableFrom(projectile)) { + launch = new EntityEnderPearl(world, getHandle()); + ((EntityThrowable) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, 0.0F, 1.5F, 1.0F); // ItemEnderPearl + } else if (Arrow.class.isAssignableFrom(projectile)) { + if (TippedArrow.class.isAssignableFrom(projectile)) { + launch = new EntityTippedArrow(world, getHandle()); + ((EntityTippedArrow) launch).setType(CraftPotionUtil.fromBukkit(new PotionData(PotionType.WATER, false, false))); + } else if (SpectralArrow.class.isAssignableFrom(projectile)) { + launch = new EntitySpectralArrow(world, getHandle()); + } else { + launch = new EntityTippedArrow(world, getHandle()); + } + ((EntityArrow) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, 0.0F, 3.0F, 1.0F); // ItemBow + } else if (ThrownPotion.class.isAssignableFrom(projectile)) { + if (LingeringPotion.class.isAssignableFrom(projectile)) { + launch = new EntityPotion(world, getHandle(), CraftItemStack.asNMSCopy(new ItemStack(Material.LINGERING_POTION, 1))); + } else { + launch = new EntityPotion(world, getHandle(), CraftItemStack.asNMSCopy(new ItemStack(Material.SPLASH_POTION, 1))); + } + ((EntityThrowable) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, -20.0F, 0.5F, 1.0F); // ItemSplashPotion + } else if (ThrownExpBottle.class.isAssignableFrom(projectile)) { + launch = new EntityExpBottle(world, getHandle()); + ((EntityThrowable) launch).shoot(getHandle(), getHandle().rotationPitch, getHandle().rotationYaw, -20.0F, 0.7F, 1.0F); // ItemExpBottle + } else if (Fish.class.isAssignableFrom(projectile) && getHandle() instanceof EntityPlayer) { + launch = new EntityFishHook(world, (EntityPlayer) getHandle()); + } else if (Fireball.class.isAssignableFrom(projectile)) { + Location location = getEyeLocation(); + Vector direction = location.getDirection().multiply(10); + + if (SmallFireball.class.isAssignableFrom(projectile)) { + launch = new EntitySmallFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ()); + } else if (WitherSkull.class.isAssignableFrom(projectile)) { + launch = new EntityWitherSkull(world, getHandle(), direction.getX(), direction.getY(), direction.getZ()); + } else if (DragonFireball.class.isAssignableFrom(projectile)) { + launch = new EntityDragonFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ()); + } else { + launch = new EntityLargeFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ()); + } + + ((EntityFireball) launch).projectileSource = this; + launch.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } else if (LlamaSpit.class.isAssignableFrom(projectile)) { + Location location = getEyeLocation(); + Vector direction = location.getDirection(); + + launch = new EntityLlamaSpit(world); + + ((EntityLlamaSpit) launch).owner = getHandle(); + ((EntityLlamaSpit) launch).shoot(direction.getX(), direction.getY(), direction.getZ(), 1.5F, 10.0F); // EntityLlama + launch.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } else if (ShulkerBullet.class.isAssignableFrom(projectile)) { + Location location = getEyeLocation(); + + launch = new EntityShulkerBullet(world, getHandle(), null, null); + launch.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + Validate.notNull(launch, "Projectile not supported"); + + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } + + world.spawnEntity(launch); + return (T) launch.getBukkitEntity(); + } + + public EntityType getType() { + return EntityType.UNKNOWN; + } + + public boolean hasLineOfSight(Entity other) { + return getHandle().canEntityBeSeen(((CraftEntity) other).getHandle()); + } + + public boolean getRemoveWhenFarAway() { + return getHandle() instanceof EntityLiving && !((EntityLiving) getHandle()).persistenceRequired; + } + + public void setRemoveWhenFarAway(boolean remove) { + if (getHandle() instanceof EntityLiving) { + ((EntityLiving) getHandle()).persistenceRequired = !remove; + } + } + + public EntityEquipment getEquipment() { + return equipment; + } + + public void setCanPickupItems(boolean pickup) { + getHandle().thisisatest = pickup; + } + + public boolean getCanPickupItems() { + return getHandle().thisisatest; + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + if (getHealth() == 0) { + return false; + } + + return super.teleport(location, cause); + } + + public boolean isLeashed() { + if (!(getHandle() instanceof EntityLiving)) { + return false; + } + return ((EntityLiving) getHandle()).getLeashHolder() != null; + } + + public Entity getLeashHolder() throws IllegalStateException { + if (!isLeashed()) { + throw new IllegalStateException("Entity not leashed"); + } + return ((EntityLiving) getHandle()).getLeashHolder().getBukkitEntity(); + } + + private boolean unleash() { + if (!isLeashed()) { + return false; + } + ((EntityLiving) getHandle()).clearLeashed(true, false); + return true; + } + + public boolean setLeashHolder(Entity holder) { + if ((getHandle() instanceof EntityWither) || !(getHandle() instanceof EntityLiving)) { + return false; + } + + if (holder == null) { + return unleash(); + } + + if (holder.isDead()) { + return false; + } + + unleash(); + ((EntityLiving) getHandle()).setLeashHolder(((CraftEntity) holder).getHandle(), true); + return true; + } + + @Override + public boolean isGliding() { + return getHandle().getFlag(7); + } + + @Override + public void setGliding(boolean gliding) { + getHandle().setFlag(7, gliding); + } + + @Override + public AttributeInstance getAttribute(Attribute attribute) { + return getHandle().craftAttributes.getAttribute(attribute); + } + + @Override + public void setAI(boolean ai) { + if (this.getHandle() instanceof EntityLiving) { + ((EntityLiving) this.getHandle()).setNoAI(!ai); + } + } + + @Override + public boolean hasAI() { + return (this.getHandle() instanceof EntityLiving) && !((EntityLiving) this.getHandle()).isAIDisabled(); + } + + @Override + public void setCollidable(boolean collidable) { + getHandle().collides = collidable; + } + + @Override + public boolean isCollidable() { + return getHandle().collides; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java new file mode 100644 index 00000000..374e4365 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java @@ -0,0 +1,67 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.passive.EntityLlama; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventoryLlama; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse; +import org.bukkit.entity.Llama; +import org.bukkit.inventory.LlamaInventory; + +public class CraftLlama extends CraftChestedHorse implements Llama { + + public CraftLlama(CraftServer server, EntityLlama entity) { + super(server, entity); + } + + @Override + public EntityLlama getHandle() { + return (EntityLlama) super.getHandle(); + } + + @Override + public Color getColor() { + return Color.values()[getHandle().getVariant()]; + } + + @Override + public void setColor(Color color) { + Preconditions.checkArgument(color != null, "color"); + + getHandle().setVariant(color.ordinal()); + } + + @Override + public LlamaInventory getInventory() { + return new CraftInventoryLlama(getHandle().horseChest); + } + + @Override + public int getStrength() { + return getHandle().getStrength(); + } + + @Override + public void setStrength(int strength) { + Preconditions.checkArgument(1 <= strength && strength <= 5, "strength must be [1,5]"); + if (strength == getStrength()) return; + getHandle().setStrength(strength); + getHandle().initHorseChest(); + } + + @Override + public Horse.Variant getVariant() { + return Horse.Variant.LLAMA; + } + + @Override + public String toString() { + return "CraftLlama"; + } + + @Override + public EntityType getType() { + return EntityType.LLAMA; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java new file mode 100644 index 00000000..db7ff69c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityLlamaSpit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LlamaSpit; +import org.bukkit.projectiles.ProjectileSource; + +public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { + + public CraftLlamaSpit(CraftServer server, EntityLlamaSpit entity) { + super(server, entity); + } + + @Override + public EntityLlamaSpit getHandle() { + return (EntityLlamaSpit) super.getHandle(); + } + + @Override + public String toString() { + return "CraftLlamaSpit"; + } + + @Override + public EntityType getType() { + return EntityType.LLAMA_SPIT; + } + + @Override + public ProjectileSource getShooter() { + return (getHandle().owner != null) ? (ProjectileSource) getHandle().owner.getBukkitEntity() : null; + } + + @Override + public void setShooter(ProjectileSource source) { + getHandle().owner = (source != null) ? ((CraftLivingEntity) source).getHandle() : null; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java new file mode 100644 index 00000000..1a9a4ad8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityMagmaCube; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.MagmaCube; + +public class CraftMagmaCube extends CraftSlime implements MagmaCube { + + public CraftMagmaCube(CraftServer server, EntityMagmaCube entity) { + super(server, entity); + } + + public EntityMagmaCube getHandle() { + return (EntityMagmaCube) entity; + } + + @Override + public String toString() { + return "CraftMagmaCube"; + } + + public EntityType getType() { + return EntityType.MAGMA_CUBE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java new file mode 100644 index 00000000..97ab3417 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java @@ -0,0 +1,93 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.init.Blocks; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Minecart; +import org.bukkit.material.MaterialData; +import org.bukkit.util.Vector; + +public class CraftMinecart extends CraftVehicle implements Minecart { + public CraftMinecart(CraftServer server, EntityMinecart entity) { + super(server, entity); + } + + public void setDamage(double damage) { + getHandle().setDamage((float) damage); + } + + public double getDamage() { + return getHandle().getDamage(); + } + + public double getMaxSpeed() { + return getHandle().maxSpeed; + } + + public void setMaxSpeed(double speed) { + if (speed >= 0D) { + getHandle().maxSpeed = speed; + } + } + + public boolean isSlowWhenEmpty() { + return getHandle().slowWhenEmpty; + } + + public void setSlowWhenEmpty(boolean slow) { + getHandle().slowWhenEmpty = slow; + } + + public Vector getFlyingVelocityMod() { + return getHandle().getFlyingVelocityMod(); + } + + public void setFlyingVelocityMod(Vector flying) { + getHandle().setFlyingVelocityMod(flying); + } + + public Vector getDerailedVelocityMod() { + return getHandle().getDerailedVelocityMod(); + } + + public void setDerailedVelocityMod(Vector derailed) { + getHandle().setDerailedVelocityMod(derailed); + } + + @Override + public EntityMinecart getHandle() { + return (EntityMinecart) entity; + } + + public void setDisplayBlock(MaterialData material) { + if(material != null) { + IBlockState block = CraftMagicNumbers.getBlock(material.getItemTypeId()).getStateFromMeta(material.getData()); + this.getHandle().setDisplayTile(block); + } else { + // Set block to air (default) and set the flag to not have a display block. + this.getHandle().setDisplayTile(Blocks.AIR.getDefaultState()); + this.getHandle().setHasDisplayTile(false); + } + } + + public MaterialData getDisplayBlock() { + IBlockState blockData = getHandle().getDisplayTile(); + return CraftMagicNumbers.getMaterial(blockData.getBlock()).getNewData((byte) blockData.getBlock().getMetaFromState(blockData)); + } + + public void setDisplayBlockOffset(int offset) { + getHandle().setDisplayTileOffset(offset); + } + + public int getDisplayBlockOffset() { + return getHandle().getDisplayTileOffset(); + } + + @Override + public EntityType getType() { + return EntityType.MINECART; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java new file mode 100644 index 00000000..58e866ad --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartChest; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.StorageMinecart; +import org.bukkit.inventory.Inventory; + +@SuppressWarnings("deprecation") +public class CraftMinecartChest extends CraftMinecart implements StorageMinecart { + private final CraftInventory inventory; + + public CraftMinecartChest(CraftServer server, EntityMinecartChest entity) { + super(server, entity); + inventory = new CraftInventory(entity); + } + + public Inventory getInventory() { + return inventory; + } + + @Override + public String toString() { + return "CraftMinecartChest{" + "inventory=" + inventory + '}'; + } + + public EntityType getType() { + return EntityType.MINECART_CHEST; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java new file mode 100644 index 00000000..5b2bbde8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java @@ -0,0 +1,132 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.Set; + +import net.minecraft.entity.item.EntityMinecartCommandBlock; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart { + private final PermissibleBase perm = new PermissibleBase(this); + + public CraftMinecartCommand(CraftServer server, EntityMinecartCommandBlock entity) { + super(server, entity); + } + + @Override + public EntityMinecartCommandBlock getHandle() { + return (EntityMinecartCommandBlock) entity; + } + + @Override + public String getCommand() { + return getHandle().getCommandBlockLogic().getCommand(); + } + + @Override + public void setCommand(String command) { + getHandle().getCommandBlockLogic().setCommand(command != null ? command : ""); + getHandle().getDataManager().set(EntityMinecartCommandBlock.COMMAND, getHandle().getCommandBlockLogic().getCommand()); + } + + @Override + public void setName(String name) { + getHandle().getCommandBlockLogic().setName(name != null ? name : "@"); + } + + @Override + public EntityType getType() { + return EntityType.MINECART_COMMAND; + } + + @Override + public void sendMessage(String message) { + } + + @Override + public void sendMessage(String[] messages) { + } + + @Override + public String getName() { + return getHandle().getCommandBlockLogic().getName(); + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of a minecart"); + } + + @Override + public boolean isPermissionSet(String name) { + return perm.isPermissionSet(name); + } + + @Override + public boolean isPermissionSet(Permission perm) { + return this.perm.isPermissionSet(perm); + } + + @Override + public boolean hasPermission(String name) { + return perm.hasPermission(name); + } + + @Override + public boolean hasPermission(Permission perm) { + return this.perm.hasPermission(perm); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return perm.addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return perm.addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return perm.addAttachment(plugin, name, value, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return perm.addAttachment(plugin, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + perm.removeAttachment(attachment); + } + + @Override + public void recalculatePermissions() { + perm.recalculatePermissions(); + } + + @Override + public Set getEffectivePermissions() { + return perm.getEffectivePermissions(); + } + + @Override + public Server getServer() { + return Bukkit.getServer(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java new file mode 100644 index 00000000..a9186611 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartFurnace; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.PoweredMinecart; + +@SuppressWarnings("deprecation") +public class CraftMinecartFurnace extends CraftMinecart implements PoweredMinecart { + public CraftMinecartFurnace(CraftServer server, EntityMinecartFurnace entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftMinecartFurnace"; + } + + public EntityType getType() { + return EntityType.MINECART_FURNACE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java new file mode 100644 index 00000000..5d5d2f49 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java @@ -0,0 +1,40 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartHopper; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.inventory.Inventory; + +final class CraftMinecartHopper extends CraftMinecart implements HopperMinecart { + private final CraftInventory inventory; + + CraftMinecartHopper(CraftServer server, EntityMinecartHopper entity) { + super(server, entity); + inventory = new CraftInventory(entity); + } + + @Override + public String toString() { + return "CraftMinecartHopper{" + "inventory=" + inventory + '}'; + } + + public EntityType getType() { + return EntityType.MINECART_HOPPER; + } + + public Inventory getInventory() { + return inventory; + } + + @Override + public boolean isEnabled() { + return ((EntityMinecartHopper) getHandle()).getBlocked(); + } + + @Override + public void setEnabled(boolean enabled) { + ((EntityMinecartHopper) getHandle()).setBlocked(enabled); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java new file mode 100644 index 00000000..0aa75f8e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java @@ -0,0 +1,21 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartMobSpawner; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.SpawnerMinecart; + +final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMinecart { + CraftMinecartMobSpawner(CraftServer server, EntityMinecartMobSpawner entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftMinecartMobSpawner"; + } + + public EntityType getType() { + return EntityType.MINECART_MOB_SPAWNER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartRideable.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartRideable.java new file mode 100644 index 00000000..8c8450a0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartRideable.java @@ -0,0 +1,21 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartEmpty; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.RideableMinecart; + +public class CraftMinecartRideable extends CraftMinecart implements RideableMinecart { + public CraftMinecartRideable(CraftServer server, EntityMinecartEmpty entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftMinecartRideable"; + } + + public EntityType getType() { + return EntityType.MINECART; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java new file mode 100644 index 00000000..266ff445 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java @@ -0,0 +1,21 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityMinecartTNT; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.minecart.ExplosiveMinecart; + +final class CraftMinecartTNT extends CraftMinecart implements ExplosiveMinecart { + CraftMinecartTNT(CraftServer server, EntityMinecartTNT entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftMinecartTNT"; + } + + public EntityType getType() { + return EntityType.MINECART_TNT; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java new file mode 100644 index 00000000..31865be8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityMob; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Monster; + +public class CraftMonster extends CraftCreature implements Monster { + + public CraftMonster(CraftServer server, EntityMob entity) { + super(server, entity); + } + + @Override + public EntityMob getHandle() { + return (EntityMob) entity; + } + + @Override + public String toString() { + return "CraftMonster"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMule.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMule.java new file mode 100644 index 00000000..4b07f30b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMule.java @@ -0,0 +1,29 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityMule; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse.Variant; +import org.bukkit.entity.Mule; + +public class CraftMule extends CraftChestedHorse implements Mule { + + public CraftMule(CraftServer server, EntityMule entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftMule"; + } + + @Override + public EntityType getType() { + return EntityType.MULE; + } + + @Override + public Variant getVariant() { + return Variant.MULE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java new file mode 100644 index 00000000..93a9a0d5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityMooshroom; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.MushroomCow; + +public class CraftMushroomCow extends CraftCow implements MushroomCow { + public CraftMushroomCow(CraftServer server, EntityMooshroom entity) { + super(server, entity); + } + + @Override + public EntityMooshroom getHandle() { + return (EntityMooshroom) entity; + } + + @Override + public String toString() { + return "CraftMushroomCow"; + } + + public EntityType getType() { + return EntityType.MUSHROOM_COW; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java new file mode 100644 index 00000000..f0524da9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityOcelot; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Ocelot; + +public class CraftOcelot extends CraftTameableAnimal implements Ocelot { + public CraftOcelot(CraftServer server, EntityOcelot ocelot) { + super(server, ocelot); + } + + @Override + public EntityOcelot getHandle() { + return (EntityOcelot) entity; + } + + public Type getCatType() { + return Type.getType(getHandle().getTameSkin()); + } + + public void setCatType(Type type) { + Validate.notNull(type, "Cat type cannot be null"); + getHandle().setTameSkin(type.getId()); + } + + @Override + public String toString() { + return "CraftOcelot"; + } + + @Override + public EntityType getType() { + return EntityType.OCELOT; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java new file mode 100644 index 00000000..b92613fc --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java @@ -0,0 +1,78 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityPainting; +import net.minecraft.entity.item.EntityPainting.EnumArt; +import net.minecraft.world.WorldServer; +import org.bukkit.Art; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftArt; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Painting; + +public class CraftPainting extends CraftHanging implements Painting { + + public CraftPainting(CraftServer server, EntityPainting entity) { + super(server, entity); + } + + public Art getArt() { + EnumArt art = getHandle().art; + return CraftArt.NotchToBukkit(art); + } + + public boolean setArt(Art art) { + return setArt(art, false); + } + + public boolean setArt(Art art, boolean force) { + EntityPainting painting = this.getHandle(); + EnumArt oldArt = painting.art; + painting.art = CraftArt.BukkitToNotch(art); + painting.updateFacingWithBoundingBox(painting.facingDirection); + if (!force && !painting.onValidSurface()) { + // Revert painting since it doesn't fit + painting.art = oldArt; + painting.updateFacingWithBoundingBox(painting.facingDirection); + return false; + } + this.update(); + return true; + } + + public boolean setFacingDirection(BlockFace face, boolean force) { + if (super.setFacingDirection(face, force)) { + update(); + return true; + } + + return false; + } + + private void update() { + WorldServer world = ((CraftWorld) getWorld()).getHandle(); + EntityPainting painting = new EntityPainting(world); + painting.hangingPosition = getHandle().getHangingPosition(); + painting.art = getHandle().art; + painting.updateFacingWithBoundingBox(getHandle().facingDirection); + getHandle().setDead(); + getHandle().velocityChanged = true; // because this occurs when the painting is broken, so it might be important + world.spawnEntity(painting); + this.entity = painting; + } + + @Override + public EntityPainting getHandle() { + return (EntityPainting) entity; + } + + @Override + public String toString() { + return "CraftPainting{art=" + getArt() + "}"; + } + + public EntityType getType() { + return EntityType.PAINTING; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java new file mode 100644 index 00000000..d3724080 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java @@ -0,0 +1,41 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.passive.EntityParrot; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Parrot; + +public class CraftParrot extends CraftTameableAnimal implements Parrot { + + public CraftParrot(CraftServer server, EntityParrot parrot) { + super(server, parrot); + } + + @Override + public EntityParrot getHandle() { + return (EntityParrot) entity; + } + + @Override + public Variant getVariant() { + return Variant.values()[getHandle().getVariant()]; + } + + @Override + public void setVariant(Variant variant) { + Preconditions.checkArgument(variant != null, "variant"); + + getHandle().setVariant(variant.ordinal()); + } + + @Override + public String toString() { + return "CraftParrot"; + } + + @Override + public EntityType getType() { + return EntityType.PARROT; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java new file mode 100644 index 00000000..46a69d3b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java @@ -0,0 +1,33 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityPig; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Pig; + +public class CraftPig extends CraftAnimals implements Pig { + public CraftPig(CraftServer server, EntityPig entity) { + super(server, entity); + } + + public boolean hasSaddle() { + return getHandle().getSaddled(); + } + + public void setSaddle(boolean saddled) { + getHandle().setSaddled(saddled); + } + + public EntityPig getHandle() { + return (EntityPig) entity; + } + + @Override + public String toString() { + return "CraftPig"; + } + + public EntityType getType() { + return EntityType.PIG; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java new file mode 100644 index 00000000..19bbfc73 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java @@ -0,0 +1,43 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityPigZombie; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.PigZombie; + +public class CraftPigZombie extends CraftZombie implements PigZombie { + + public CraftPigZombie(CraftServer server, EntityPigZombie entity) { + super(server, entity); + } + + public int getAnger() { + return getHandle().angerLevel; + } + + public void setAnger(int level) { + getHandle().angerLevel = level; + } + + public void setAngry(boolean angry) { + setAnger(angry ? 400 : 0); + } + + public boolean isAngry() { + return getAnger() > 0; + } + + @Override + public EntityPigZombie getHandle() { + return (EntityPigZombie) entity; + } + + @Override + public String toString() { + return "CraftPigZombie"; + } + + public EntityType getType() { + return EntityType.PIG_ZOMBIE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java new file mode 100644 index 00000000..a73163b7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -0,0 +1,1748 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.BaseEncoding; +import com.mojang.authlib.GameProfile; +import io.netty.buffer.Unpooled; + +import io.netty.util.internal.ConcurrentSet; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; + +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.advancements.PlayerAdvancements; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityTracker; +import net.minecraft.entity.EntityTrackerEntry; +import net.minecraft.entity.ai.attributes.AttributeMap; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance; +import net.minecraft.entity.ai.attributes.RangedAttribute; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.nbt.NBTTagCompound; + +import net.minecraft.network.NetHandlerPlayServer; +import net.minecraft.network.Packet; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.SPacketBlockChange; +import net.minecraft.network.play.server.SPacketChat; +import net.minecraft.network.play.server.SPacketCustomPayload; +import net.minecraft.network.play.server.SPacketCustomSound; +import net.minecraft.network.play.server.SPacketEffect; +import net.minecraft.network.play.server.SPacketEntityProperties; +import net.minecraft.network.play.server.SPacketMaps; +import net.minecraft.network.play.server.SPacketParticles; +import net.minecraft.network.play.server.SPacketPlayerListItem; +import net.minecraft.network.play.server.SPacketSoundEffect; +import net.minecraft.network.play.server.SPacketSpawnPosition; +import net.minecraft.network.play.server.SPacketTitle; +import net.minecraft.network.play.server.SPacketUpdateHealth; +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.ChatType; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldServer; +import net.minecraft.world.storage.MapDecoration; +import net.minecraftforge.common.util.FakePlayer; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.NotImplementedException; +import org.bukkit.*; +import org.bukkit.BanList; +import org.bukkit.Statistic; +import org.bukkit.Material; +import org.bukkit.Statistic.Type; +import org.bukkit.World; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.conversations.ManuallyAbandonedConversationCanceller; +import org.bukkit.craftbukkit.CraftParticle; +import org.bukkit.craftbukkit.block.CraftSign; +import org.bukkit.craftbukkit.conversations.ConversationTracker; +import org.bukkit.craftbukkit.CraftEffect; +import org.bukkit.craftbukkit.CraftOfflinePlayer; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftSound; +import org.bukkit.craftbukkit.CraftStatistic; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.advancement.CraftAdvancement; +import org.bukkit.craftbukkit.advancement.CraftAdvancementProgress; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.craftbukkit.map.RenderData; +import org.bukkit.craftbukkit.scoreboard.CraftScoreboard; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRegisterChannelEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerUnregisterChannelEvent; +import org.bukkit.inventory.InventoryView.Property; +import org.bukkit.map.MapCursor; +import org.bukkit.map.MapView; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.StandardMessenger; +import org.bukkit.scoreboard.Scoreboard; + +import javax.annotation.Nullable; +import org.spigotmc.AsyncCatcher; +import org.spigotmc.SpigotConfig; + +@DelegateDeserialization(CraftOfflinePlayer.class) +public class CraftPlayer extends CraftHumanEntity implements Player { + private long firstPlayed = 0; + private long lastPlayed = 0; + private boolean hasPlayedBefore = false; + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new ConcurrentSet<>(); + private final Map>> hiddenPlayers = new HashMap<>(); + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; + private double health = 20; + private boolean scaledHealth = false; + private double healthScale = 20; + + public CraftPlayer(CraftServer server, EntityPlayerMP entity) { + super(server, entity); + + firstPlayed = System.currentTimeMillis(); + } + + public GameProfile getProfile() { + return getHandle().getGameProfile(); + } + + @Override + public boolean isOp() { + return server.getHandle().canSendCommands(getProfile()); + } + + @Override + public void setOp(boolean value) { + if (value == isOp()) { + return; + } + + if (value) { + server.getHandle().addOp(getProfile()); + } else { + server.getHandle().removeOp(getProfile()); + } + + perm.recalculatePermissions(); + } + + public boolean isOnline() { + return server.getPlayer(getUniqueId()) != null; + } + + public InetSocketAddress getAddress() { + if (getHandle().connection == null) { + return null; + } + + SocketAddress addr = getHandle().connection.netManager.getRemoteAddress(); + if (addr instanceof InetSocketAddress) { + return (InetSocketAddress) addr; + } else { + return null; + } + } + + @Override + public double getEyeHeight(boolean ignorePose) { + if (ignorePose) { + return 1.62D; + } else { + return getEyeHeight(); + } + } + + @Override + public void sendRawMessage(String message) { + if (getHandle().connection == null) { + return; + } + + for (ITextComponent component : CraftChatMessage.fromString(message)) { + getHandle().connection.sendPacket(new SPacketChat(component)); + } + } + + @Override + public void sendMessage(String message) { + if (!conversationTracker.isConversingModaly()) { + this.sendRawMessage(message); + } + } + + @Override + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + @Override + public String getDisplayName() { + return getHandle().displayName; + } + + @Override + public void setDisplayName(final String name) { + getHandle().displayName = name == null ? getName() : name; + } + + @Override + public String getPlayerListName() { + return getHandle().listName == null ? getName() : CraftChatMessage.fromComponent(getHandle().listName, TextFormatting.WHITE); + } + + @Override + public void setPlayerListName(String name) { + if (name == null) { + name = getName(); + } + getHandle().listName = name.equals(getName()) ? null : CraftChatMessage.fromString(name)[0]; + for (EntityPlayerMP player : server.getHandle().getPlayers()) { + if (player.getBukkitEntity().canSee(this)) { + player.connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.UPDATE_DISPLAY_NAME, getHandle())); + } + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof OfflinePlayer)) { + return false; + } + OfflinePlayer other = (OfflinePlayer) obj; + if ((this.getUniqueId() == null) || (other.getUniqueId() == null)) { + return false; + } + + boolean uuidEquals = this.getUniqueId().equals(other.getUniqueId()); + boolean idEquals = true; + + if (other instanceof CraftPlayer) { + idEquals = this.getEntityId() == ((CraftPlayer) other).getEntityId(); + } + + return uuidEquals && idEquals; + } + + @Override + public void kickPlayer(String message) { + AsyncCatcher.catchOp( "player kick"); // Spigot + if (getHandle().connection == null) { + return; + } + + getHandle().connection.disconnect(message == null ? "" : message); + } + + @Override + public void setCompassTarget(Location loc) { + if (getHandle().connection == null) return; + + // Do not directly assign here, from the packethandler we'll assign it. + getHandle().connection.sendPacket(new SPacketSpawnPosition(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))); + } + + @Override + public Location getCompassTarget() { + return getHandle().compassTarget; + } + + @Override + public void chat(String msg) { + if (getHandle().connection == null) return; + + getHandle().connection.chat(msg, false); + } + + @Override + public boolean performCommand(String command) { + return server.dispatchCommand(this, command); + } + + @Override + public void playNote(Location loc, byte instrument, byte note) { + if (getHandle().connection == null) return; + + String instrumentName = null; + switch (instrument) { + case 0: + instrumentName = "harp"; + break; + case 1: + instrumentName = "basedrum"; + break; + case 2: + instrumentName = "snare"; + break; + case 3: + instrumentName = "hat"; + break; + case 4: + instrumentName = "bass"; + break; + case 5: + instrumentName = "flute"; + break; + case 6: + instrumentName = "bell"; + break; + case 7: + instrumentName = "guitar"; + break; + case 8: + instrumentName = "chime"; + break; + case 9: + instrumentName = "xylophone"; + break; + } + + float f = (float) Math.pow(2.0D, (note - 12.0D) / 12.0D); + getHandle().connection.sendPacket(new SPacketSoundEffect(CraftSound.getSoundEffect("block.note." + instrumentName),net.minecraft.util.SoundCategory.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, f)); + } + + @Override + public void playNote(Location loc, Instrument instrument, Note note) { + if (getHandle().connection == null) return; + + String instrumentName = null; + switch (instrument.ordinal()) { + case 0: + instrumentName = "harp"; + break; + case 1: + instrumentName = "basedrum"; + break; + case 2: + instrumentName = "snare"; + break; + case 3: + instrumentName = "hat"; + break; + case 4: + instrumentName = "bass"; + break; + case 5: + instrumentName = "flute"; + break; + case 6: + instrumentName = "bell"; + break; + case 7: + instrumentName = "guitar"; + break; + case 8: + instrumentName = "chime"; + break; + case 9: + instrumentName = "xylophone"; + break; + } + float f = (float) Math.pow(2.0D, (note.getId() - 12.0D) / 12.0D); + getHandle().connection.sendPacket(new SPacketSoundEffect(CraftSound.getSoundEffect("block.note." + instrumentName), net.minecraft.util.SoundCategory.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, f)); + } + + @Override + public void playSound(Location loc, Sound sound, float volume, float pitch) { + playSound(loc, sound, SoundCategory.MASTER, volume, pitch); + } + + @Override + public void playSound(Location loc, String sound, float volume, float pitch) { + playSound(loc, sound, SoundCategory.MASTER, volume, pitch); + } + + @Override + public void playSound(Location loc, Sound sound, SoundCategory category, float volume, float pitch) { + if (loc == null || sound == null || category == null || getHandle().connection == null) return; + + SPacketSoundEffect packet = new SPacketSoundEffect(CraftSound.getSoundEffect(CraftSound.getSound(sound)), net.minecraft.util.SoundCategory.valueOf(category.name()), loc.getX(), loc.getY(), loc.getZ(), volume, pitch); + getHandle().connection.sendPacket(packet); + } + + @Override + public void playSound(Location loc, String sound, SoundCategory category, float volume, float pitch) { + if (loc == null || sound == null || category == null || getHandle().connection == null) return; + + SPacketCustomSound packet = new SPacketCustomSound(sound, net.minecraft.util.SoundCategory.valueOf(category.name()), loc.getX(), loc.getY(), loc.getZ(), volume, pitch); + getHandle().connection.sendPacket(packet); + } + + @Override + public void stopSound(Sound sound) { + stopSound(sound, null); + } + + @Override + public void stopSound(String sound) { + stopSound(sound, null); + } + + @Override + public void stopSound(Sound sound, SoundCategory category) { + stopSound(CraftSound.getSound(sound), category); + } + + @Override + public void stopSound(String sound, SoundCategory category) { + if (getHandle().connection == null) return; + PacketBuffer packetdataserializer = new PacketBuffer(Unpooled.buffer()); + + packetdataserializer.writeString(category == null ? "" : net.minecraft.util.SoundCategory.valueOf(category.name()).getName()); + packetdataserializer.writeString(sound); + getHandle().connection.sendPacket(new SPacketCustomPayload("MC|StopSound", packetdataserializer)); + } + + @Override + public void playEffect(Location loc, Effect effect, int data) { + if (getHandle().connection == null) { + return; + } + spigot().playEffect(loc, effect, data, 0, 0, 0, 0, 1, 1, 64); // Spigot + } + + @Override + public void playEffect(Location loc, Effect effect, T data) { + if (data != null) { + Validate.isTrue(effect.getData() != null && effect.getData().isAssignableFrom(data.getClass()), "Wrong kind of data for this effect!"); + } else { + Validate.isTrue(effect.getData() == null, "Wrong kind of data for this effect!"); + } + + int datavalue = data == null ? 0 : CraftEffect.getDataValue(effect, data); + playEffect(loc, effect, datavalue); + } + + @Override + public void sendBlockChange(Location loc, Material material, byte data) { + sendBlockChange(loc, material.getId(), data); + } + + @Override + public void sendBlockChange(Location loc, int material, byte data) { + if (getHandle().connection == null) return; + + SPacketBlockChange packet = new SPacketBlockChange(((CraftWorld) loc.getWorld()).getHandle(), new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + + packet.blockState = CraftMagicNumbers.getBlock(material).getStateFromMeta(data); + getHandle().connection.sendPacket(packet); + } + + @Override + public void sendSignChange(Location loc, String[] lines) { + if (getHandle().connection == null) { + return; + } + + if (lines == null) { + lines = new String[4]; + } + + Validate.notNull(loc, "Location can not be null"); + if (lines.length < 4) { + throw new IllegalArgumentException("Must have at least 4 lines"); + } + + ITextComponent[] components = CraftSign.sanitizeLines(lines); + TileEntitySign sign = new TileEntitySign(); + sign.setPos(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + System.arraycopy(components, 0, sign.signText, 0, sign.signText.length); + + getHandle().connection.sendPacket(sign.getUpdatePacket()); + } + + @Override + public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data) { + if (getHandle().connection == null) return false; + + /* + int x = loc.getBlockX(); + int y = loc.getBlockY(); + int z = loc.getBlockZ(); + + int cx = x >> 4; + int cz = z >> 4; + + if (sx <= 0 || sy <= 0 || sz <= 0) { + return false; + } + + if ((x + sx - 1) >> 4 != cx || (z + sz - 1) >> 4 != cz || y < 0 || y + sy > 128) { + return false; + } + + if (data.length != (sx * sy * sz * 5) / 2) { + return false; + } + + Packet51MapChunk packet = new Packet51MapChunk(x, y, z, sx, sy, sz, data); + + getHandle().connection.sendPacket(packet); + + return true; + */ + + throw new NotImplementedException("Chunk changes do not yet work"); // TODO: Chunk changes. + } + + @Override + public void sendMap(MapView map) { + if (getHandle().connection == null) return; + + RenderData data = ((CraftMapView) map).render(this); + Collection icons = new ArrayList<>(); + for (MapCursor cursor : data.cursors) { + if (cursor.isVisible()) { + icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection())); + } + } + + SPacketMaps packet = new SPacketMaps(map.getId(), map.getScale().getValue(), true, icons, data.buffer, 0, 0, 128, 128); + getHandle().connection.sendPacket(packet); + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + Preconditions.checkArgument(location != null, "location"); + Preconditions.checkArgument(location.getWorld() != null, "location.world"); + location.checkFinite(); + EntityPlayerMP entity = getHandle(); + + if (getHealth() == 0 || entity.isDead || entity instanceof FakePlayer) { + return false; + } + + if (entity.connection == null) { + return false; + } + + if (entity.isBeingRidden()) { + return false; + } + + // From = Players current Location + Location from = this.getLocation(); + // To = Players new Location if Teleport is Successful + Location to = location; + // Create & Call the Teleport Event. + PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); + server.getPluginManager().callEvent(event); + + // Return False to inform the Plugin that the Teleport was unsuccessful/cancelled. + if (event.isCancelled()) { + return false; + } + + // If this player is riding another entity, we must dismount before teleporting. + entity.dismountRidingEntity(); + + // Update the From Location + from = event.getFrom(); + // Grab the new To Location dependent on whether the event was cancelled. + to = event.getTo(); + // Grab the To and From World Handles. + WorldServer fromWorld = ((CraftWorld) from.getWorld()).getHandle(); + WorldServer toWorld = ((CraftWorld) to.getWorld()).getHandle(); + + // Close any foreign inventory + if (getHandle().openContainer != getHandle().inventoryContainer) { + getHandle().closeScreen(); + } + + // Check if the fromWorld and toWorld are the same. + if (fromWorld == toWorld) { + entity.connection.teleport(to); + } else { + server.getHandle().moveToWorld(entity, toWorld.dimension, true, to, true); + } + return true; + } + + @Override + public void setSneaking(boolean sneak) { + getHandle().setSneaking(sneak); + } + + @Override + public boolean isSneaking() { + return getHandle().isSneaking(); + } + + @Override + public boolean isSprinting() { + return getHandle().isSprinting(); + } + + @Override + public void setSprinting(boolean sprinting) { + getHandle().setSprinting(sprinting); + } + + @Override + public void loadData() { + server.getHandle().playerDataManager.readPlayerData(getHandle()); + } + + @Override + public void saveData() { + server.getHandle().playerDataManager.writePlayerData(getHandle()); + } + + @Override + public void updateInventory() { + getHandle().sendContainerToPlayer(getHandle().openContainer); + } + + @Override + public void setSleepingIgnored(boolean isSleeping) { + getHandle().fauxSleeping = isSleeping; + ((CraftWorld) getWorld()).getHandle().checkSleepStatus(); + } + + @Override + public boolean isSleepingIgnored() { + return getHandle().fauxSleeping; + } + + @Override + public void awardAchievement(Achievement achievement) { + throw new UnsupportedOperationException("Not supported in this Minecraft version."); + } + + @Override + public void removeAchievement(Achievement achievement) { + throw new UnsupportedOperationException("Not supported in this Minecraft version."); + } + + @Override + public boolean hasAchievement(Achievement achievement) { + throw new UnsupportedOperationException("Not supported in this Minecraft version."); + } + + @Override + public void incrementStatistic(Statistic statistic) { + incrementStatistic(statistic, 1); + } + + @Override + public void decrementStatistic(Statistic statistic) { + decrementStatistic(statistic, 1); + } + + @Override + public int getStatistic(Statistic statistic) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.isTrue(statistic.getType() == Type.UNTYPED, "Must supply additional paramater for this statistic"); + return getHandle().getStatFile().readStat(CraftStatistic.getNMSStatistic(statistic)); + } + + @Override + public void incrementStatistic(Statistic statistic, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, getStatistic(statistic) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, getStatistic(statistic) - amount); + } + + @Override + public void setStatistic(Statistic statistic, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.isTrue(statistic.getType() == Type.UNTYPED, "Must supply additional paramater for this statistic"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + net.minecraft.stats.StatBase nmsStatistic = CraftStatistic.getNMSStatistic(statistic); + getHandle().getStatFile().unlockAchievement(getHandle(), nmsStatistic, newValue); + } + + @Override + public void incrementStatistic(Statistic statistic, Material material) { + incrementStatistic(statistic, material, 1); + } + + @Override + public void decrementStatistic(Statistic statistic, Material material) { + decrementStatistic(statistic, material, 1); + } + + @Override + public int getStatistic(Statistic statistic, Material material) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(statistic.getType() == Type.BLOCK || statistic.getType() == Type.ITEM, "This statistic does not take a Material parameter"); + net.minecraft.stats.StatBase nmsStatistic = CraftStatistic.getMaterialStatistic(statistic, material); + Validate.notNull(nmsStatistic, "The supplied Material does not have a corresponding statistic"); + return getHandle().getStatFile().readStat(nmsStatistic); + } + + @Override + public void incrementStatistic(Statistic statistic, Material material, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, material, getStatistic(statistic, material) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, Material material, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, material, getStatistic(statistic, material) - amount); + } + + @Override + public void setStatistic(Statistic statistic, Material material, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + Validate.isTrue(statistic.getType() == Type.BLOCK || statistic.getType() == Type.ITEM, "This statistic does not take a Material parameter"); + net.minecraft.stats.StatBase nmsStatistic = CraftStatistic.getMaterialStatistic(statistic, material); + Validate.notNull(nmsStatistic, "The supplied Material does not have a corresponding statistic"); + getHandle().getStatFile().unlockAchievement(getHandle(), nmsStatistic, newValue); + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType) { + incrementStatistic(statistic, entityType, 1); + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType) { + decrementStatistic(statistic, entityType, 1); + } + + @Override + public int getStatistic(Statistic statistic, EntityType entityType) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(entityType, "EntityType cannot be null"); + Validate.isTrue(statistic.getType() == Type.ENTITY, "This statistic does not take an EntityType parameter"); + net.minecraft.stats.StatBase nmsStatistic = CraftStatistic.getEntityStatistic(statistic, entityType); + Validate.notNull(nmsStatistic, "The supplied EntityType does not have a corresponding statistic"); + return getHandle().getStatFile().readStat(nmsStatistic); + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, entityType, getStatistic(statistic, entityType) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, entityType, getStatistic(statistic, entityType) - amount); + } + + @Override + public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(entityType, "EntityType cannot be null"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + Validate.isTrue(statistic.getType() == Type.ENTITY, "This statistic does not take an EntityType parameter"); + net.minecraft.stats.StatBase nmsStatistic = CraftStatistic.getEntityStatistic(statistic, entityType); + Validate.notNull(nmsStatistic, "The supplied EntityType does not have a corresponding statistic"); + getHandle().getStatFile().unlockAchievement(getHandle(), nmsStatistic, newValue); + } + + @Override + public void setPlayerTime(long time, boolean relative) { + getHandle().timeOffset = time; + getHandle().relativeTime = relative; + } + + @Override + public long getPlayerTimeOffset() { + return getHandle().timeOffset; + } + + @Override + public long getPlayerTime() { + return getHandle().getPlayerTime(); + } + + @Override + public boolean isPlayerTimeRelative() { + return getHandle().relativeTime; + } + + @Override + public void resetPlayerTime() { + setPlayerTime(0, true); + } + + @Override + public void setPlayerWeather(WeatherType type) { + getHandle().setPlayerWeather(type, true); + } + + @Override + public WeatherType getPlayerWeather() { + return getHandle().getPlayerWeather(); + } + + @Override + public void resetPlayerWeather() { + getHandle().resetPlayerWeather(); + } + + @Override + public boolean isBanned() { + return server.getBanList(BanList.Type.NAME).isBanned(getName()); + } + + @Override + public boolean isWhitelisted() { + return server.getHandle().getWhitelistedPlayers().isWhitelisted(getProfile()); + } + + @Override + public void setWhitelisted(boolean value) { + if (value) { + server.getHandle().addWhitelistedPlayer(getProfile()); + } else { + server.getHandle().removePlayerFromWhitelist(getProfile()); + } + } + + @Override + public void setGameMode(GameMode mode) { + if (getHandle().connection == null) return; + + if (mode == null) { + throw new IllegalArgumentException("Mode cannot be null"); + } + + getHandle().setGameType(GameType.getByID(mode.getValue())); + } + + @Override + public GameMode getGameMode() { + return GameMode.getByValue(getHandle().interactionManager.getGameType().getID()); + } + + @Override + public void giveExp(int exp) { + getHandle().addExperience(exp); + } + + @Override + public void giveExpLevels(int levels) { + getHandle().addExperienceLevel(levels); + } + + @Override + public float getExp() { + return getHandle().experience; + } + + @Override + public void setExp(float exp) { + Preconditions.checkArgument(exp >= 0.0 && exp <= 1.0, "Experience progress must be between 0.0 and 1.0 (%s)", exp); + getHandle().experience = exp; + getHandle().lastExperience = -1; + } + + @Override + public int getLevel() { + return getHandle().experienceLevel; + } + + @Override + public void setLevel(int level) { + getHandle().experienceLevel = level; + getHandle().lastExperience = -1; + } + + @Override + public int getTotalExperience() { + return getHandle().experienceTotal; + } + + @Override + public void setTotalExperience(int exp) { + getHandle().experienceTotal = exp; + } + + @Override + public float getExhaustion() { + return getHandle().getFoodStats().foodExhaustionLevel; + } + + @Override + public void setExhaustion(float value) { + getHandle().getFoodStats().foodExhaustionLevel = value; + } + + @Override + public float getSaturation() { + return getHandle().getFoodStats().foodSaturationLevel; + } + + @Override + public void setSaturation(float value) { + getHandle().getFoodStats().foodSaturationLevel = value; + } + + @Override + public int getFoodLevel() { + return getHandle().getFoodStats().foodLevel; + } + + @Override + public void setFoodLevel(int value) { + getHandle().getFoodStats().foodLevel = value; + } + + @Override + public Location getBedSpawnLocation() { + World world = getServer().getWorld(getHandle().spawnWorld); + BlockPos bed = getHandle().getBedLocation(); + + if (world != null && bed != null) { + bed = EntityPlayer.getBedSpawnLocation(((CraftWorld) world).getHandle(), bed, getHandle().isSpawnForced()); + if (bed != null) { + return new Location(world, bed.getX(), bed.getY(), bed.getZ()); + } + } + return null; + } + + @Override + public void setBedSpawnLocation(Location location) { + setBedSpawnLocation(location, false); + } + + @Override + public void setBedSpawnLocation(Location location, boolean override) { + if (location == null) { + getHandle().setSpawnPoint(null, override); + } else { + getHandle().setSpawnPoint(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), override); + getHandle().spawnWorld = location.getWorld().getName(); + } + } + + @Nullable + private static WeakReference getPluginWeakReference(@Nullable Plugin plugin) { + return (plugin == null) ? null : pluginWeakReferences.computeIfAbsent(plugin, WeakReference::new); + } + + @Override + @Deprecated + public void hidePlayer(Player player) { + hidePlayer0(null, player); + } + + @Override + public void hidePlayer(Plugin plugin, Player player) { + Validate.notNull(plugin, "Plugin cannot be null"); + Validate.isTrue(plugin.isEnabled(), "Plugin attempted to hide player while disabled"); + + hidePlayer0(plugin, player); + } + + private void hidePlayer0(@Nullable Plugin plugin, Player player) { + Validate.notNull(player, "hidden player cannot be null"); + if (getHandle().connection == null) return; + if (equals(player)) return; + + Set> hidingPlugins = hiddenPlayers.get(player.getUniqueId()); + if (hidingPlugins != null) { + // Some plugins are already hiding the player. Just mark that this + // plugin wants the player hidden too and end. + hidingPlugins.add(getPluginWeakReference(plugin)); + return; + } + hidingPlugins = new HashSet<>(); + hidingPlugins.add(getPluginWeakReference(plugin)); + hiddenPlayers.put(player.getUniqueId(), hidingPlugins); + + // Remove this player from the hidden player's EntityTrackerEntry + EntityTracker tracker = ((WorldServer) entity.world).entityTracker; + EntityPlayerMP other = ((CraftPlayer) player).getHandle(); + EntityTrackerEntry entry = tracker.trackedEntityHashTable.lookup(other.getEntityId()); + if (entry != null) { + entry.removeTrackedPlayerSymmetric(getHandle()); + } + + // Remove the hidden player from this player user list, if they're on it + if (other.sentListPacket) { + getHandle().connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.REMOVE_PLAYER, other)); + } + } + + @Override + @Deprecated + public void showPlayer(Player player) { + showPlayer0(null, player); + } + + @Override + public void showPlayer(Plugin plugin, Player player) { + Validate.notNull(plugin, "Plugin cannot be null"); + // Don't require that plugin be enabled. A plugin must be allowed to call + // showPlayer during its onDisable() method. + showPlayer0(plugin, player); + } + + private void showPlayer0(@Nullable Plugin plugin, Player player) { + Validate.notNull(player, "shown player cannot be null"); + if (getHandle().connection == null) return; + if (equals(player)) return; + + Set> hidingPlugins = hiddenPlayers.get(player.getUniqueId()); + if (hidingPlugins == null) { + return; // Player isn't hidden + } + hidingPlugins.remove(getPluginWeakReference(plugin)); + if (!hidingPlugins.isEmpty()) { + return; // Some other plugins still want the player hidden + } + hiddenPlayers.remove(player.getUniqueId()); + + EntityTracker tracker = ((WorldServer) entity.world).entityTracker; + EntityPlayerMP other = ((CraftPlayer) player).getHandle(); + + getHandle().connection.sendPacket(new SPacketPlayerListItem(SPacketPlayerListItem.Action.ADD_PLAYER, other)); + + EntityTrackerEntry entry = tracker.trackedEntityHashTable.lookup(other.getEntityId()); + if (entry != null && !entry.trackingPlayers.contains(getHandle())) { + entry.updatePlayerEntity(getHandle()); + } + } + + public void removeDisconnectingPlayer(Player player) { + hiddenPlayers.remove(player.getUniqueId()); + } + + @Override + public boolean canSee(Player player) { + return !hiddenPlayers.containsKey(player.getUniqueId()); + } + + @Override + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("name", getName()); + + return result; + } + + @Override + public Player getPlayer() { + return this; + } + + @Override + public EntityPlayerMP getHandle() { + return (EntityPlayerMP) entity; + } + + public void setHandle(final EntityPlayer entity) { + super.setHandle(entity); + } + + @Override + public String toString() { + return "CraftPlayer{" + "name=" + getName() + '}'; + } + + @Override + public int hashCode() { + if (hash == 0 || hash == 485) { + hash = 97 * 5 + (this.getUniqueId() != null ? this.getUniqueId().hashCode() : 0); + } + return hash; + } + + @Override + public long getFirstPlayed() { + return firstPlayed; + } + + @Override + public long getLastPlayed() { + return lastPlayed; + } + + @Override + public boolean hasPlayedBefore() { + return hasPlayedBefore; + } + + public void setFirstPlayed(long firstPlayed) { + this.firstPlayed = firstPlayed; + } + + public void readExtraData(NBTTagCompound nbttagcompound) { + hasPlayedBefore = true; + if (nbttagcompound.hasKey("bukkit")) { + NBTTagCompound data = nbttagcompound.getCompoundTag("bukkit"); + + if (data.hasKey("firstPlayed")) { + firstPlayed = data.getLong("firstPlayed"); + lastPlayed = data.getLong("lastPlayed"); + } + + if (data.hasKey("newExp")) { + EntityPlayerMP handle = getHandle(); + handle.newExp = data.getInteger("newExp"); + handle.newTotalExp = data.getInteger("newTotalExp"); + handle.newLevel = data.getInteger("newLevel"); + handle.expToDrop = data.getInteger("expToDrop"); + handle.keepLevel = data.getBoolean("keepLevel"); + } + } + } + + public void setExtraData(NBTTagCompound nbttagcompound) { + if (!nbttagcompound.hasKey("bukkit")) { + nbttagcompound.setTag("bukkit", new NBTTagCompound()); + } + + NBTTagCompound data = nbttagcompound.getCompoundTag("bukkit"); + EntityPlayerMP handle = getHandle(); + data.setInteger("newExp", handle.newExp); + data.setInteger("newTotalExp", handle.newTotalExp); + data.setInteger("newLevel", handle.newLevel); + data.setInteger("expToDrop", handle.expToDrop); + data.setBoolean("keepLevel", handle.keepLevel); + data.setLong("firstPlayed", getFirstPlayed()); + data.setLong("lastPlayed", System.currentTimeMillis()); + data.setString("lastKnownName", handle.getName()); + } + + @Override + public boolean beginConversation(Conversation conversation) { + return conversationTracker.beginConversation(conversation); + } + + @Override + public void abandonConversation(Conversation conversation) { + conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller())); + } + + @Override + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + conversationTracker.abandonConversation(conversation, details); + } + + @Override + public void acceptConversationInput(String input) { + conversationTracker.acceptConversationInput(input); + } + + @Override + public boolean isConversing() { + return conversationTracker.isConversing(); + } + + @Override + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message); + if (getHandle().connection == null) return; + + if (channels.contains(channel) || SpigotConfig.bungee) { + SPacketCustomPayload packet = new SPacketCustomPayload(channel, new PacketBuffer(Unpooled.wrappedBuffer(message))); + getHandle().connection.sendPacket(packet); + } + } + + @Override + public int getViewDistance() { + return this.getHandle().getViewDistance(); + } + + @Override + public void setViewDistance(final int viewDistance) { + ((WorldServer)this.getHandle().world).getPlayerChunkMap().updateViewDistance(this.getHandle(), viewDistance); + } + + @Override + public void setTexturePack(String url) { + setResourcePack(url); + } + + @Override + public void setResourcePack(String url) { + Validate.notNull(url, "Resource pack URL cannot be null"); + + getHandle().loadResourcePack(url, "null"); + } + + @Override + public void setResourcePack(String url, byte[] hash) { + Validate.notNull(url, "Resource pack URL cannot be null"); + Validate.notNull(hash, "Resource pack hash cannot be null"); + Validate.isTrue(hash.length == 20, "Resource pack hash should be 20 bytes long but was " + hash.length); + + getHandle().loadResourcePack(url, BaseEncoding.base16().lowerCase().encode(hash)); + } + + public void addChannel(String channel) { + if (channels.add(channel)) { + server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); + } + } + + public void removeChannel(String channel) { + if (channels.remove(channel)) { + server.getPluginManager().callEvent(new PlayerUnregisterChannelEvent(this, channel)); + } + } + + @Override + public Set getListeningPluginChannels() { + return ImmutableSet.copyOf(channels); + } + + public void sendSupportedChannels() { + if (getHandle().connection == null) return; + Set listening = server.getMessenger().getIncomingChannels(); + + if (!listening.isEmpty()) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + for (String channel : listening) { + try { + stream.write(channel.getBytes(StandardCharsets.UTF_8)); + stream.write((byte) 0); + } catch (IOException ex) { + Logger.getLogger(CraftPlayer.class.getName()).log(Level.SEVERE, "Could not send Plugin Channel REGISTER to " + getName(), ex); + } + } + + getHandle().connection.sendPacket(new SPacketCustomPayload("REGISTER", new PacketBuffer(Unpooled.wrappedBuffer(stream.toByteArray())))); + } + } + + @Override + public EntityType getType() { + return EntityType.PLAYER; + } + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + server.getPlayerMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + @Override + public List getMetadata(String metadataKey) { + return server.getPlayerMetadata().getMetadata(this, metadataKey); + } + + @Override + public boolean hasMetadata(String metadataKey) { + return server.getPlayerMetadata().hasMetadata(this, metadataKey); + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + server.getPlayerMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + + @Override + public boolean setWindowProperty(Property prop, int value) { + Container container = getHandle().openContainer; + if (container.getBukkitView().getType() != prop.getType()) { + return false; + } + getHandle().sendWindowProperty(container, prop.getId(), value); + return true; + } + + public void disconnect(String reason) { + conversationTracker.abandonAllConversations(); + perm.clearPermissions(); + } + + @Override + public boolean isFlying() { + return getHandle().capabilities.isFlying; + } + + @Override + public void setFlying(boolean value) { + if (!getAllowFlight() && value) { + throw new IllegalArgumentException("Cannot make player fly if getAllowFlight() is false"); + } + + getHandle().capabilities.isFlying = value; + getHandle().sendPlayerAbilities(); + } + + @Override + public boolean getAllowFlight() { + return getHandle().capabilities.allowFlying; + } + + @Override + public void setAllowFlight(boolean value) { + if (isFlying() && !value) { + getHandle().capabilities.isFlying = false; + } + + getHandle().capabilities.allowFlying = value; + getHandle().sendPlayerAbilities(); + } + + @Override + public int getNoDamageTicks() { + if (getHandle().respawnInvulnerabilityTicks > 0) { + return Math.max(getHandle().respawnInvulnerabilityTicks, getHandle().hurtResistantTime); + } else { + return getHandle().hurtResistantTime; + } + } + + @Override + public void setFlySpeed(float value) { + validateSpeed(value); + EntityPlayer player = getHandle(); + player.capabilities.flySpeed = value / 2f; + player.sendPlayerAbilities(); + + } + + @Override + public void setWalkSpeed(float value) { + validateSpeed(value); + EntityPlayer player = getHandle(); + player.capabilities.walkSpeed = value / 2f; + player.sendPlayerAbilities(); + } + + @Override + public float getFlySpeed() { + return getHandle().capabilities.flySpeed * 2f; + } + + @Override + public float getWalkSpeed() { + return getHandle().capabilities.walkSpeed * 2f; + } + + private void validateSpeed(float value) { + if (value < 0) { + if (value < -1f) { + throw new IllegalArgumentException(value + " is too low"); + } + } else { + if (value > 1f) { + throw new IllegalArgumentException(value + " is too high"); + } + } + } + + @Override + public void setMaxHealth(double amount) { + super.setMaxHealth(amount); + this.health = Math.min(this.health, health); + getHandle().setPlayerHealthUpdated(); + } + + @Override + public void resetMaxHealth() { + super.resetMaxHealth(); + getHandle().setPlayerHealthUpdated(); + } + + @Override + public CraftScoreboard getScoreboard() { + return this.server.getScoreboardManager().getPlayerBoard(this); + } + + @Override + public void setScoreboard(Scoreboard scoreboard) { + Validate.notNull(scoreboard, "Scoreboard cannot be null"); + NetHandlerPlayServer connection = getHandle().connection; + if (connection == null) { + return; + } + if (connection.isDisconnected()) { + } + + this.server.getScoreboardManager().setPlayerBoard(this, scoreboard); + } + + @Override + public void setHealthScale(double value) { + Validate.isTrue((float) value > 0F, "Must be greater than 0"); + healthScale = value; + scaledHealth = true; + updateScaledHealth(); + } + + @Override + public double getHealthScale() { + return healthScale; + } + + @Override + public void setHealthScaled(boolean scale) { + if (scaledHealth != (scaledHealth = scale)) { + updateScaledHealth(); + } + } + + @Override + public boolean isHealthScaled() { + return scaledHealth; + } + + public float getScaledHealth() { + return (float) (isHealthScaled() ? getHealth() * getHealthScale() / getMaxHealth() : getHealth()); + } + + @Override + public double getHealth() { + return health; + } + + public void setRealHealth(double health) { + if (Double.isNaN(health)) {return;} // Paper + this.health = health; + } + + public void updateScaledHealth() { + AttributeMap attributemapserver = (AttributeMap) getHandle().getAttributeMap(); + Collection set = attributemapserver.getWatchedAttributes(); // PAIL: Rename + + injectScaledMaxHealth(set, true); + + // SPIGOT-3813: Attributes before health + if (getHandle().connection != null) { + getHandle().connection.sendPacket(new SPacketEntityProperties(getHandle().getEntityId(), set)); + sendHealthUpdate(); + } + getHandle().getDataManager().set(EntityLiving.HEALTH, (float) getScaledHealth()); + + getHandle().maxHealthCache = getMaxHealth(); + } + + public void sendHealthUpdate() { + getHandle().connection.sendPacket(new SPacketUpdateHealth(getScaledHealth(), getHandle().getFoodStats().getFoodLevel(), getHandle().getFoodStats().getSaturationLevel())); + } + + public void injectScaledMaxHealth(Collection collection, boolean force) { + if (!scaledHealth && !force) { + return; + } + for (IAttributeInstance genericInstance : collection) { + if (genericInstance.getAttribute().getName().equals("generic.maxHealth")) { + collection.remove(genericInstance); + break; + } + } + // Spigot start + double healthMod = scaledHealth ? healthScale : getMaxHealth(); + if ( healthMod >= Float.MAX_VALUE || healthMod <= 0 ) + { + healthMod = 20; // Reset health + server.getLogger().warning( getName() + " tried to crash the server with a large health attribute" ); + } + collection.add(new ModifiableAttributeInstance(getHandle().getAttributeMap(), (new RangedAttribute(null, "generic.maxHealth", healthMod, 0.0D, Float.MAX_VALUE)).setDescription("Max Health").setShouldWatch(true))); + // Spigot end + } + + @Override + public org.bukkit.entity.Entity getSpectatorTarget() { + Entity followed = getHandle().getSpectatingEntity(); + return followed == getHandle() ? null : followed.getBukkitEntity(); + } + + @Override + public void setSpectatorTarget(org.bukkit.entity.Entity entity) { + Preconditions.checkArgument(getGameMode() == GameMode.SPECTATOR, "Player must be in spectator mode"); + getHandle().setSpectatingEntity((entity == null) ? null : ((CraftEntity) entity).getHandle()); + } + + @Override + public void sendTitle(String title, String subtitle) { + sendTitle(title, subtitle, 10, 70, 20); + } + + @Override + public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut) { + SPacketTitle times = new SPacketTitle(fadeIn, stay, fadeOut); + getHandle().connection.sendPacket(times); + + if (title != null) { + SPacketTitle packetTitle = new SPacketTitle(SPacketTitle.Type.TITLE, CraftChatMessage.fromString(title)[0]); + getHandle().connection.sendPacket(packetTitle); + } + + if (subtitle != null) { + SPacketTitle packetSubtitle = new SPacketTitle(SPacketTitle.Type.SUBTITLE, CraftChatMessage.fromString(subtitle)[0]); + getHandle().connection.sendPacket(packetSubtitle); + } + } + + @Override + public void resetTitle() { + if (getHandle().connection == null) { + return; + } + SPacketTitle packetReset = new SPacketTitle(SPacketTitle.Type.RESET, null); + getHandle().connection.sendPacket(packetReset); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count) { + spawnParticle(particle, x, y, z, count, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { + spawnParticle(particle, x, y, z, count, 0, 0, 0, data); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, 1, data); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + if (getHandle().connection == null) { + return; + } + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } + SPacketParticles packetplayoutworldparticles = new SPacketParticles(CraftParticle.toNMS(particle), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count, CraftParticle.toData(particle, data)); + getHandle().connection.sendPacket(packetplayoutworldparticles); + + } + + @Override + public org.bukkit.advancement.AdvancementProgress getAdvancementProgress(org.bukkit.advancement.Advancement advancement) { + Preconditions.checkArgument(advancement != null, "advancement"); + + CraftAdvancement craft = (CraftAdvancement) advancement; + PlayerAdvancements data = getHandle().getAdvancements(); + AdvancementProgress progress = data.getProgress(craft.getHandle()); + + return new CraftAdvancementProgress(craft, data, progress); + } + + @Override + public String getLocale() { + return getHandle().language; + } + + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { + @Override + public InetSocketAddress getRawAddress() + { + return (InetSocketAddress) getHandle().connection.netManager.getRawAddress(); + } + + @Override + public boolean getCollidesWithEntities() { + return CraftPlayer.this.isCollidable(); + } + + @Override + public void setCollidesWithEntities(boolean collides) { + CraftPlayer.this.setCollidable(collides); + } + + public void respawn() + { + if ( getHealth() <= 0 && isOnline() ) + { + server.getServer().getPlayerList().recreatePlayerEntity( getHandle(), 0, false ); + } + } + + @Override + public void playEffect( Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius ) + { + Validate.notNull( location, "Location cannot be null" ); + Validate.notNull( effect, "Effect cannot be null" ); + Validate.notNull( location.getWorld(), "World cannot be null" ); + Packet packet; + if ( effect.getType() != Effect.Type.PARTICLE ) + { + int packetData = effect.getId(); + packet = new SPacketEffect( packetData, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ() ), id, false ); + } else + { + net.minecraft.util.EnumParticleTypes particle = null; + int[] extra = null; + for ( net.minecraft.util.EnumParticleTypes p : net.minecraft.util.EnumParticleTypes.values() ) + { + if ( effect.getName().startsWith( p.getParticleName().replace("_", "") ) ) + { + particle = p; + if ( effect.getData() != null ) + { + if ( effect.getData().equals( Material.class ) ) + { + extra = new int[]{ id }; + } else + { + extra = new int[]{ (data << 12) | (id & 0xFFF) }; + } + } + break; + } + } + if ( extra == null ) + { + extra = new int[0]; + } + packet = new SPacketParticles( particle, true, (float) location.getX(), (float) location.getY(), (float) location.getZ(), offsetX, offsetY, offsetZ, speed, particleCount, extra ); + } + int distance; + radius *= radius; + if ( getHandle().connection == null ) + { + return; + } + if ( !location.getWorld().equals( getWorld() ) ) + { + return; + } + + distance = (int) getLocation().distanceSquared( location ); + if ( distance <= radius ) + { + getHandle().connection.sendPacket( packet ); + } + } + + public String getLocale() + { + return getHandle().language; + } + + public Set getHiddenPlayers() + { + Set ret = new HashSet(); + for ( UUID u : hiddenPlayers.keySet() ) + { + ret.add( getServer().getPlayer( u ) ); + } + + return java.util.Collections.unmodifiableSet( ret ); + } +@Override + public void sendMessage(BaseComponent component) { + sendMessage( new BaseComponent[] { component } ); + } + + @Override + public void sendMessage(BaseComponent... components) { + if ( getHandle().connection == null ) { + return; + } + + SPacketChat packet = new SPacketChat(null, ChatType.CHAT); + packet.components = components; + getHandle().connection.sendPacket(packet); + } + + @Override + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, BaseComponent component) { + sendMessage( position, new BaseComponent[] { component } ); + } + + @Override + public void sendMessage(ChatMessageType position, BaseComponent... components) { + if ( getHandle().connection == null ) { + return; + } + + SPacketChat packet = new SPacketChat(null, ChatType.byId((byte) position.ordinal())); + // Action bar doesn't render colours, replace colours with legacy section symbols + if (position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR) { + components = new BaseComponent[]{new net.md_5.bungee.api.chat.TextComponent(BaseComponent.toLegacyText(components))}; + } + packet.components = components; + getHandle().connection.sendPacket(packet); + } + }; + + @Override + public Player.Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java new file mode 100644 index 00000000..d6f222ab --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityPolarBear; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.PolarBear; + +public class CraftPolarBear extends CraftAnimals implements PolarBear { + + public CraftPolarBear(CraftServer server, EntityPolarBear entity) { + super(server, entity); + } + @Override + public EntityPolarBear getHandle() { + return (EntityPolarBear) entity; + } + + @Override + public String toString() { + return "CraftPolarBear"; + } + + @Override + public EntityType getType() { + return EntityType.POLAR_BEAR; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java new file mode 100644 index 00000000..b9e70b83 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.projectile.EntityThrowable; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Projectile; +import org.bukkit.projectiles.ProjectileSource; + +public class CraftProjectile extends AbstractProjectile implements Projectile { + public CraftProjectile(CraftServer server, net.minecraft.entity.Entity entity) { + super(server, entity); + } + + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + public void setShooter(ProjectileSource shooter) { + if (shooter instanceof CraftLivingEntity) { + getHandle().thrower = (EntityLiving) ((CraftLivingEntity) shooter).entity; + if (shooter instanceof CraftHumanEntity) { + getHandle().throwerName = ((CraftHumanEntity) shooter).getName(); + } + } else { + getHandle().thrower = null; + getHandle().throwerName = null; + } + getHandle().projectileSource = shooter; + } + + @Override + public EntityThrowable getHandle() { + return (EntityThrowable) entity; + } + + @Override + public String toString() { + return "CraftProjectile"; + } + + @Override + public EntityType getType() { + return EntityType.UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java new file mode 100644 index 00000000..fe60d245 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java @@ -0,0 +1,88 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.ai.EntityAITasks; +import net.minecraft.entity.passive.EntityRabbit; +import net.minecraft.world.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Rabbit; +import org.bukkit.craftbukkit.CraftWorld; + +public class CraftRabbit extends CraftAnimals implements Rabbit { + + public CraftRabbit(CraftServer server, EntityRabbit entity) { + super(server, entity); + } + + @Override + public EntityRabbit getHandle() { + return (EntityRabbit) entity; + } + + @Override + public String toString() { + return "CraftRabbit{RabbitType=" + getRabbitType() + "}"; + } + + @Override + public EntityType getType() { + return EntityType.RABBIT; + } + + @Override + public Type getRabbitType() { + int type = getHandle().getRabbitType(); + return CraftMagicMapping.fromMagic(type); + } + + @Override + public void setRabbitType(Type type) { + EntityRabbit entity = getHandle(); + if (getRabbitType() == Type.THE_KILLER_BUNNY) { + // Reset goals and target finders. + World world = ((CraftWorld) this.getWorld()).getHandle(); + entity.tasks = new EntityAITasks(world != null && world.profiler != null ? world.profiler : null); + entity.targetTasks = new EntityAITasks(world != null && world.profiler != null ? world.profiler : null); + entity.initializePathFinderGoals(); + } + + entity.setRabbitType(CraftMagicMapping.toMagic(type)); + } + + private static class CraftMagicMapping { + + private static final int[] types = new int[Type.values().length]; + private static final Type[] reverse = new Type[Type.values().length]; + + static { + set(Type.BROWN, 0); + set(Type.WHITE, 1); + set(Type.BLACK, 2); + set(Type.BLACK_AND_WHITE, 3); + set(Type.GOLD, 4); + set(Type.SALT_AND_PEPPER, 5); + set(Type.THE_KILLER_BUNNY, 99); + } + + private static void set(Type type, int value) { + types[type.ordinal()] = value; + if (value < reverse.length) { + reverse[value] = type; + } + } + + public static Type fromMagic(int magic) { + if (magic >= 0 && magic < reverse.length) { + return reverse[magic]; + } else if (magic == 99) { + return Type.THE_KILLER_BUNNY; + } else { + return null; + } + } + + public static int toMagic(Type type) { + return types[type.ordinal()]; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java new file mode 100644 index 00000000..c527b890 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java @@ -0,0 +1,44 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntitySheep; +import net.minecraft.item.EnumDyeColor; +import org.bukkit.DyeColor; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Sheep; + +public class CraftSheep extends CraftAnimals implements Sheep { + public CraftSheep(CraftServer server, EntitySheep entity) { + super(server, entity); + } + + public DyeColor getColor() { + return DyeColor.getByWoolData((byte) getHandle().getFleeceColor().getMetadata()); + } + + public void setColor(DyeColor color) { + getHandle().setFleeceColor(EnumDyeColor.byMetadata(color.getWoolData())); + } + + public boolean isSheared() { + return getHandle().getSheared(); + } + + public void setSheared(boolean flag) { + getHandle().setSheared(flag); + } + + @Override + public EntitySheep getHandle() { + return (EntitySheep) entity; + } + + @Override + public String toString() { + return "CraftSheep"; + } + + public EntityType getType() { + return EntityType.SHEEP; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java new file mode 100644 index 00000000..7fc1daeb --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.monster.EntityShulker; +import org.bukkit.DyeColor; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Shulker; + +public class CraftShulker extends CraftGolem implements Shulker { + + public CraftShulker(CraftServer server, EntityShulker entity) { + super(server, entity); + } + + @Override + public EntityType getType() { + return EntityType.SHULKER; + } + + @Override + public EntityShulker getHandle() { + return (EntityShulker) entity; + } + + @Override + public DyeColor getColor() { + return DyeColor.getByWoolData(getHandle().getDataManager().get(EntityShulker.COLOR)); + } + + @Override + public void setColor(DyeColor color) { + Preconditions.checkArgument(color != null, "color"); + + getHandle().getDataManager().set(EntityShulker.COLOR, color.getWoolData()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java new file mode 100644 index 00000000..633e5b40 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java @@ -0,0 +1,50 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityShulkerBullet; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.ShulkerBullet; +import org.bukkit.projectiles.ProjectileSource; + +public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBullet { + + public CraftShulkerBullet(CraftServer server, EntityShulkerBullet entity) { + super(server, entity); + } + + @Override + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + @Override + public void setShooter(ProjectileSource shooter) { + if (shooter instanceof LivingEntity) { + getHandle().setShooter(((CraftLivingEntity) shooter).getHandle()); + } else { + getHandle().setShooter(null); + } + getHandle().projectileSource = shooter; + } + + @Override + public org.bukkit.entity.Entity getTarget() { + return getHandle().getTarget() != null ? getHandle().getTarget().getBukkitEntity() : null; + } + + @Override + public void setTarget(org.bukkit.entity.Entity target) { + getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); + } + + @Override + public EntityType getType() { + return EntityType.SHULKER_BULLET; + } + + @Override + public EntityShulkerBullet getHandle() { + return (EntityShulkerBullet) entity; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java new file mode 100644 index 00000000..952e6d1b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntitySilverfish; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Silverfish; + +public class CraftSilverfish extends CraftMonster implements Silverfish { + public CraftSilverfish(CraftServer server, EntitySilverfish entity) { + super(server, entity); + } + + @Override + public EntitySilverfish getHandle() { + return (EntitySilverfish) entity; + } + + @Override + public String toString() { + return "CraftSilverfish"; + } + + public EntityType getType() { + return EntityType.SILVERFISH; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java new file mode 100644 index 00000000..fc57ba99 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.AbstractSkeleton; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Skeleton; + +public class CraftSkeleton extends CraftMonster implements Skeleton { + + public CraftSkeleton(CraftServer server, AbstractSkeleton entity) { + super(server, entity); + } + + @Override + public AbstractSkeleton getHandle() { + return (AbstractSkeleton) entity; + } + + @Override + public String toString() { + return "CraftSkeleton"; + } + + public EntityType getType() { + return EntityType.SKELETON; + } + + @Override + public SkeletonType getSkeletonType() { + return SkeletonType.NORMAL; + } + + @Override + public void setSkeletonType(SkeletonType type) { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java new file mode 100644 index 00000000..46fdf81d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java @@ -0,0 +1,29 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntitySkeletonHorse; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse.Variant; +import org.bukkit.entity.SkeletonHorse; + +public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHorse { + + public CraftSkeletonHorse(CraftServer server, EntitySkeletonHorse entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftSkeletonHorse"; + } + + @Override + public EntityType getType() { + return EntityType.SKELETON_HORSE; + } + + @Override + public Variant getVariant() { + return Variant.SKELETON_HORSE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java new file mode 100644 index 00000000..49675cad --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java @@ -0,0 +1,50 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntitySlime; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Slime; + +public class CraftSlime extends CraftLivingEntity implements Slime { + + public CraftSlime(CraftServer server, EntitySlime entity) { + super(server, entity); + } + + public int getSize() { + return getHandle().getSlimeSize(); + } + + public void setSize(int size) { + getHandle().setSlimeSize(size, true); + } + + @Override + public void setTarget(LivingEntity target) { + if (target == null) { + getHandle().setAttackTarget(null, null, false); + } else if (target instanceof CraftLivingEntity) { + getHandle().setAttackTarget(((CraftLivingEntity) target).getHandle(), null, false); + } + } + + @Override + public LivingEntity getTarget() { + return getHandle().getAttackTarget() == null ? null : (LivingEntity)getHandle().getAttackTarget().getBukkitEntity(); + } + + @Override + public EntitySlime getHandle() { + return (EntitySlime) entity; + } + + @Override + public String toString() { + return "CraftSlime"; + } + + public EntityType getType() { + return EntityType.SLIME; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java new file mode 100644 index 00000000..8a5a6795 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntitySmallFireball; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.SmallFireball; + +public class CraftSmallFireball extends CraftFireball implements SmallFireball { + public CraftSmallFireball(CraftServer server, EntitySmallFireball entity) { + super(server, entity); + } + + @Override + public EntitySmallFireball getHandle() { + return (EntitySmallFireball) entity; + } + + @Override + public String toString() { + return "CraftSmallFireball"; + } + + public EntityType getType() { + return EntityType.SMALL_FIREBALL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java new file mode 100644 index 00000000..409c4264 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntitySnowball; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Snowball; + +public class CraftSnowball extends CraftProjectile implements Snowball { + public CraftSnowball(CraftServer server, EntitySnowball entity) { + super(server, entity); + } + + @Override + public EntitySnowball getHandle() { + return (EntitySnowball) entity; + } + + @Override + public String toString() { + return "CraftSnowball"; + } + + public EntityType getType() { + return EntityType.SNOWBALL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java new file mode 100644 index 00000000..9e893071 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntitySnowman; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Snowman; + +public class CraftSnowman extends CraftGolem implements Snowman { + public CraftSnowman(CraftServer server, EntitySnowman entity) { + super(server, entity); + } + + @Override + public boolean isDerp() { + return !getHandle().isPumpkinEquipped(); + } + + @Override + public void setDerp(boolean derpMode) { + getHandle().setPumpkinEquipped(!derpMode); + } + + @Override + public EntitySnowman getHandle() { + return (EntitySnowman) entity; + } + + @Override + public String toString() { + return "CraftSnowman"; + } + + public EntityType getType() { + return EntityType.SNOWMAN; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java new file mode 100644 index 00000000..8c7af562 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java @@ -0,0 +1,38 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntitySpectralArrow; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.SpectralArrow; + +public class CraftSpectralArrow extends CraftArrow implements SpectralArrow { + + public CraftSpectralArrow(CraftServer server, EntitySpectralArrow entity) { + super(server, entity); + } + + @Override + public EntitySpectralArrow getHandle() { + return (EntitySpectralArrow) entity; + } + + @Override + public String toString() { + return "CraftSpectralArrow"; + } + + @Override + public EntityType getType() { + return EntityType.SPECTRAL_ARROW; + } + + @Override + public int getGlowingTicks() { + return getHandle().duration; + } + + @Override + public void setGlowingTicks(int duration) { + getHandle().duration = duration; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java new file mode 100644 index 00000000..d227ff60 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java @@ -0,0 +1,35 @@ +package org.bukkit.craftbukkit.entity; + +import com.google.common.base.Preconditions; +import net.minecraft.entity.monster.EntitySpellcasterIllager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Spellcaster; + +public class CraftSpellcaster extends CraftIllager implements Spellcaster { + + public CraftSpellcaster(CraftServer server, EntitySpellcasterIllager entity) { + super(server, entity); + } + + @Override + public EntitySpellcasterIllager getHandle() { + return (EntitySpellcasterIllager) super.getHandle(); + } + + @Override + public String toString() { + return "CraftSpellcaster"; + } + + @Override + public Spell getSpell() { + return Spell.valueOf(getHandle().getSpellType().name()); + } + + @Override + public void setSpell(Spell spell) { + Preconditions.checkArgument(spell != null, "Use Spell.NONE"); + + getHandle().setSpellType(EntitySpellcasterIllager.SpellType.getFromId(spell.ordinal())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java new file mode 100644 index 00000000..b6bde570 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntitySpider; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Spider; + +public class CraftSpider extends CraftMonster implements Spider { + + public CraftSpider(CraftServer server, EntitySpider entity) { + super(server, entity); + } + + @Override + public EntitySpider getHandle() { + return (EntitySpider) entity; + } + + @Override + public String toString() { + return "CraftSpider"; + } + + public EntityType getType() { + return EntityType.SPIDER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSplashPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSplashPotion.java new file mode 100644 index 00000000..dc244d96 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSplashPotion.java @@ -0,0 +1,43 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityPotion; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.SplashPotion; +import org.bukkit.inventory.ItemStack; + +public class CraftSplashPotion extends CraftThrownPotion implements SplashPotion { + + public CraftSplashPotion(CraftServer server, EntityPotion entity) { + super(server, entity); + } + + @Override + public void setItem(ItemStack item) { + // The ItemStack must not be null. + Validate.notNull(item, "ItemStack cannot be null."); + + // The ItemStack must be a potion. + Validate.isTrue(item.getType() == Material.SPLASH_POTION, "ItemStack must be a splash potion. This item stack was " + item.getType() + "."); + + getHandle().setItem(CraftItemStack.asNMSCopy(item)); + } + + @Override + public EntityPotion getHandle() { + return (EntityPotion) entity; + } + + @Override + public String toString() { + return "CraftSplashPotion"; + } + + @Override + public EntityType getType() { + return EntityType.SPLASH_POTION; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java new file mode 100644 index 00000000..0eb8fbf9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntitySquid; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Squid; + +public class CraftSquid extends CraftWaterMob implements Squid { + + public CraftSquid(CraftServer server, EntitySquid entity) { + super(server, entity); + } + + @Override + public EntitySquid getHandle() { + return (EntitySquid) entity; + } + + @Override + public String toString() { + return "CraftSquid"; + } + + public EntityType getType() { + return EntityType.SQUID; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftStray.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftStray.java new file mode 100644 index 00000000..4b0e542f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftStray.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityStray; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Stray; + +public class CraftStray extends CraftSkeleton implements Stray { + + public CraftStray(CraftServer server, EntityStray entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftStray"; + } + + @Override + public EntityType getType() { + return EntityType.STRAY; + } + + @Override + public SkeletonType getSkeletonType() { + return SkeletonType.STRAY; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java new file mode 100644 index 00000000..ebbb47a8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java @@ -0,0 +1,59 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityTNTPrimed; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.TNTPrimed; + +public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { + + public CraftTNTPrimed(CraftServer server, EntityTNTPrimed entity) { + super(server, entity); + } + + public float getYield() { + return getHandle().yield; + } + + public boolean isIncendiary() { + return getHandle().isIncendiary; + } + + public void setIsIncendiary(boolean isIncendiary) { + getHandle().isIncendiary = isIncendiary; + } + + public void setYield(float yield) { + getHandle().yield = yield; + } + + public int getFuseTicks() { + return getHandle().getFuse(); + } + + public void setFuseTicks(int fuseTicks) { + getHandle().setFuse(fuseTicks); + } + + @Override + public EntityTNTPrimed getHandle() { + return (EntityTNTPrimed) entity; + } + + @Override + public String toString() { + return "CraftTNTPrimed"; + } + + public EntityType getType() { + return EntityType.PRIMED_TNT; + } + + public Entity getSource() { + EntityLivingBase source = getHandle().getTntPlacedBy(); + + return (source != null) ? source.getBukkitEntity() : null; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java new file mode 100644 index 00000000..33109a6b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java @@ -0,0 +1,81 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityTameable; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Creature; +import org.bukkit.entity.Tameable; + +import java.util.UUID; + +public class CraftTameableAnimal extends CraftAnimals implements Tameable, Creature { + public CraftTameableAnimal(CraftServer server, EntityTameable entity) { + super(server, entity); + } + + @Override + public EntityTameable getHandle() { + return (EntityTameable)super.getHandle(); + } + + public UUID getOwnerUUID() { + try { + return getHandle().getOwnerId(); + } catch (IllegalArgumentException ex) { + return null; + } + } + + public void setOwnerUUID(UUID uuid) { + getHandle().setOwnerId(uuid); + } + + public AnimalTamer getOwner() { + if (getOwnerUUID() == null) { + return null; + } + + AnimalTamer owner = getServer().getPlayer(getOwnerUUID()); + if (owner == null) { + owner = getServer().getOfflinePlayer(getOwnerUUID()); + } + + return owner; + } + + public boolean isTamed() { + return getHandle().isTamed(); + } + + public void setOwner(AnimalTamer tamer) { + if (tamer != null) { + setTamed(true); + getHandle().setAttackTarget(null, null, false); + setOwnerUUID(tamer.getUniqueId()); + } else { + setTamed(false); + setOwnerUUID(null); + } + } + + public void setTamed(boolean tame) { + getHandle().setTamed(tame); + if (!tame) { + setOwnerUUID(null); + } + } + + public boolean isSitting() { + return getHandle().isSitting(); + } + + public void setSitting(boolean sitting) { + getHandle().setSitting(sitting); + getHandle().getAISit().setSitting(sitting); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{owner=" + getOwner() + ",tamed=" + isTamed() + "}"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java new file mode 100644 index 00000000..052113ab --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.item.EntityExpBottle; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ThrownExpBottle; + +public class CraftThrownExpBottle extends CraftProjectile implements ThrownExpBottle { + public CraftThrownExpBottle(CraftServer server, EntityExpBottle entity) { + super(server, entity); + } + + @Override + public EntityExpBottle getHandle() { + return (EntityExpBottle) entity; + } + + @Override + public String toString() { + return "EntityThrownExpBottle"; + } + + public EntityType getType() { + return EntityType.THROWN_EXP_BOTTLE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java new file mode 100644 index 00000000..6c8ef903 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.Collection; + +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.potion.PotionUtils; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +import com.google.common.collect.ImmutableList; + +public abstract class CraftThrownPotion extends CraftProjectile implements ThrownPotion { + public CraftThrownPotion(CraftServer server, EntityPotion entity) { + super(server, entity); + } + + public Collection getEffects() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (net.minecraft.potion.PotionEffect effect : PotionUtils.getEffectsFromStack(getHandle().getPotion())) { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + return builder.build(); + } + + public ItemStack getItem() { + return CraftItemStack.asBukkitCopy(getHandle().getPotion()); + } + + @Override + public EntityPotion getHandle() { + return (EntityPotion) entity; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTippedArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTippedArrow.java new file mode 100644 index 00000000..b51738a7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTippedArrow.java @@ -0,0 +1,131 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.List; + +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.potion.Potion; +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.TippedArrow; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +import com.google.common.collect.ImmutableList; + +public class CraftTippedArrow extends CraftArrow implements TippedArrow { + + public CraftTippedArrow(CraftServer server, EntityTippedArrow entity) { + super(server, entity); + } + + @Override + public EntityTippedArrow getHandle() { + return (EntityTippedArrow) entity; + } + + @Override + public String toString() { + return "CraftTippedArrow"; + } + + @Override + public EntityType getType() { + return EntityType.TIPPED_ARROW; + } + + @Override + public boolean addCustomEffect(PotionEffect effect, boolean override) { + int effectId = effect.getType().getId(); + net.minecraft.potion.PotionEffect existing = null; + for (net.minecraft.potion.PotionEffect mobEffect : getHandle().customPotionEffects) { + if (Potion.getIdFromPotion(mobEffect.getPotion()) == effectId) { + existing = mobEffect; + } + } + if (existing != null) { + if (!override) { + return false; + } + getHandle().customPotionEffects.remove(existing); + } + getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); + getHandle().refreshEffects(); + return true; + } + + @Override + public void clearCustomEffects() { + Validate.isTrue(getBasePotionData().getType() != PotionType.UNCRAFTABLE, "Tipped Arrows must have at least 1 effect"); + getHandle().customPotionEffects.clear(); + getHandle().refreshEffects(); + } + + @Override + public List getCustomEffects() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (net.minecraft.potion.PotionEffect effect : getHandle().customPotionEffects) { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + return builder.build(); + } + + @Override + public boolean hasCustomEffect(PotionEffectType type) { + for (net.minecraft.potion.PotionEffect effect : getHandle().customPotionEffects) { + if (CraftPotionUtil.equals(effect.getPotion(), type)) { + return true; + } + } + return false; + } + + @Override + public boolean hasCustomEffects() { + return !getHandle().customPotionEffects.isEmpty(); + } + + @Override + public boolean removeCustomEffect(PotionEffectType effect) { + int effectId = effect.getId(); + net.minecraft.potion.PotionEffect existing = null; + for (net.minecraft.potion.PotionEffect mobEffect : getHandle().customPotionEffects) { + if (Potion.getIdFromPotion(mobEffect.getPotion()) == effectId) { + existing = mobEffect; + } + } + if (existing == null) { + return false; + } + Validate.isTrue(getBasePotionData().getType() != PotionType.UNCRAFTABLE || !getHandle().customPotionEffects.isEmpty(), "Tipped Arrows must have at least 1 effect"); + getHandle().customPotionEffects.remove(existing); + getHandle().refreshEffects(); + return true; + } + + @Override + public void setBasePotionData(PotionData data) { + Validate.notNull(data, "PotionData cannot be null"); + Validate.isTrue(data.getType() != PotionType.UNCRAFTABLE || !getHandle().customPotionEffects.isEmpty(), "Tipped Arrows must have at least 1 effect"); + getHandle().setType(CraftPotionUtil.fromBukkit(data)); + } + + @Override + public PotionData getBasePotionData() { + return CraftPotionUtil.toBukkit(getHandle().getType()); + } + + @Override + public void setColor(Color color) { + getHandle().setFixedColor(color.asRGB()); + } + + @Override + public Color getColor() { + return Color.fromRGB(getHandle().getColor()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVehicle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVehicle.java new file mode 100644 index 00000000..2202fb48 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVehicle.java @@ -0,0 +1,15 @@ +package org.bukkit.craftbukkit.entity; + +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Vehicle; + +public abstract class CraftVehicle extends CraftEntity implements Vehicle { + public CraftVehicle(CraftServer server, net.minecraft.entity.Entity entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftVehicle{passenger=" + getPassenger() + '}'; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java new file mode 100644 index 00000000..d0334c02 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityVex; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Vex; + +public class CraftVex extends CraftMonster implements Vex { + + public CraftVex(CraftServer server, EntityVex entity) { + super(server, entity); + } + + @Override + public EntityVex getHandle() { + return (EntityVex) super.getHandle(); + } + + @Override + public String toString() { + return "CraftVex"; + } + + @Override + public EntityType getType() { + return EntityType.VEX; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java new file mode 100644 index 00000000..8aa23ac4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -0,0 +1,165 @@ +package org.bukkit.craftbukkit.entity; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +import net.minecraft.entity.passive.EntityVillager; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftMerchant; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Villager; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.MerchantRecipe; + +public class CraftVillager extends CraftAgeable implements Villager, InventoryHolder { + + private static final Map careerIDMap = new HashMap<>(); + private CraftMerchant merchant; + + public CraftVillager(CraftServer server, EntityVillager entity) { + super(server, entity); + } + + @Override + public EntityVillager getHandle() { + return (EntityVillager) entity; + } + + @Override + public String toString() { + return "CraftVillager"; + } + + public EntityType getType() { + return EntityType.VILLAGER; + } + + public Profession getProfession() { + return Profession.values()[getHandle().getProfession() + 1]; // Offset by 1 from the zombie types + } + + public void setProfession(Profession profession) { + Validate.notNull(profession); + Validate.isTrue(!profession.isZombie(), "Profession is reserved for Zombies: ", profession); + getHandle().setProfession(profession.ordinal() - 1); + } + + @Override + public Career getCareer() { + return getCareer(getProfession(), getHandle().careerId); + } + + @Override + public void setCareer(Career career) { + setCareer(career, true); + } + + @Override + public void setCareer(Career career, boolean resetTrades) { + if (career == null) { + getHandle().careerId = 0; // reset career + } else { + Validate.isTrue(career.getProfession() == getProfession(), "Career assignment mismatch. Found (" + getProfession() + ") Required (" + career.getProfession() + ")"); + getHandle().careerId = getCareerID(career); + } + + if (resetTrades) { + getHandle().buyingList = null; + getHandle().populateBuyingList(); + } + } + + @Override + public Inventory getInventory() { + return new CraftInventory(getHandle().villagerInventory); + } + + private CraftMerchant getMerchant() { + return (merchant == null) ? merchant = new CraftMerchant(getHandle()) : merchant; + } + + @Override + public List getRecipes() { + return getMerchant().getRecipes(); + } + + @Override + public void setRecipes(List recipes) { + this.getMerchant().setRecipes(recipes); + } + + @Override + public MerchantRecipe getRecipe(int i) { + return getMerchant().getRecipe(i); + } + + @Override + public void setRecipe(int i, MerchantRecipe merchantRecipe) { + getMerchant().setRecipe(i, merchantRecipe); + } + + @Override + public int getRecipeCount() { + return getMerchant().getRecipeCount(); + } + + @Override + public boolean isTrading() { + return getTrader() != null; + } + + @Override + public HumanEntity getTrader() { + return getMerchant().getTrader(); + } + + @Override + public int getRiches() { + return getHandle().wealth; + } + + @Override + public void setRiches(int riches) { + getHandle().wealth = riches; + } + + @Nullable + private static Career getCareer(Profession profession, int id) { + Validate.isTrue(id > 0, "Career id must be greater than 0"); + + List careers = profession.getCareers(); + for (Career c : careers) { + if (careerIDMap.containsKey(c) && careerIDMap.get(c) == id) { + return c; + } + } + + return null; + } + + private static int getCareerID(Career career) { + return careerIDMap.getOrDefault(career, 0); + } + + static { + // build Career -> ID map + int id = 0; + for (Profession prof : Profession.values()) { + List careers = prof.getCareers(); + if (!careers.isEmpty()) { + for (Career c : careers) { + careerIDMap.put(c, ++id); + } + } + + Validate.isTrue(id == careers.size(), "Career id registration mismatch"); + id = 0; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java new file mode 100644 index 00000000..04e1b875 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityZombieVillager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Villager; +import org.bukkit.entity.ZombieVillager; + +public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { + + public CraftVillagerZombie(CraftServer server, EntityZombieVillager entity) { + super(server, entity); + } + + @Override + public EntityZombieVillager getHandle() { + return (EntityZombieVillager) super.getHandle(); + } + + @Override + public String toString() { + return "CraftVillagerZombie"; + } + + @Override + public EntityType getType() { + return EntityType.ZOMBIE_VILLAGER; + } + + @Override + public Villager.Profession getVillagerProfession() { + return Villager.Profession.values()[getHandle().getProfession() + Villager.Profession.FARMER.ordinal()]; + } + + @Override + public void setVillagerProfession(Villager.Profession profession) { + getHandle().setProfession(profession == null ? 0 : profession.ordinal() - Villager.Profession.FARMER.ordinal()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java new file mode 100644 index 00000000..352e088d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityVindicator; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Vindicator; + +public class CraftVindicator extends CraftIllager implements Vindicator { + + public CraftVindicator(CraftServer server, EntityVindicator entity) { + super(server, entity); + } + + @Override + public EntityVindicator getHandle() { + return (EntityVindicator) super.getHandle(); + } + + @Override + public String toString() { + return "CraftVindicator"; + } + + @Override + public EntityType getType() { + return EntityType.VINDICATOR; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java new file mode 100644 index 00000000..ff80170d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityWaterMob; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.WaterMob; + +public class CraftWaterMob extends CraftLivingEntity implements WaterMob { + + public CraftWaterMob(CraftServer server, EntityWaterMob entity) { + super(server, entity); + } + + @Override + public EntityWaterMob getHandle() { + return (EntityWaterMob) entity; + } + + @Override + public String toString() { + return "CraftWaterMob"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWeather.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWeather.java new file mode 100644 index 00000000..8af0f7d6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWeather.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.effect.EntityWeatherEffect; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Weather; + +public class CraftWeather extends CraftEntity implements Weather { + public CraftWeather(final CraftServer server, final EntityWeatherEffect entity) { + super(server, entity); + } + + @Override + public EntityWeatherEffect getHandle() { + return (EntityWeatherEffect) entity; + } + + @Override + public String toString() { + return "CraftWeather"; + } + + public EntityType getType() { + return EntityType.WEATHER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java new file mode 100644 index 00000000..be9a59be --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityWitch; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Witch; +import org.bukkit.entity.EntityType; + +public class CraftWitch extends CraftMonster implements Witch { + public CraftWitch(CraftServer server, EntityWitch entity) { + super(server, entity); + } + + @Override + public EntityWitch getHandle() { + return (EntityWitch) entity; + } + + @Override + public String toString() { + return "CraftWitch"; + } + + public EntityType getType() { + return EntityType.WITCH; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java new file mode 100644 index 00000000..cd7469f2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.boss.EntityWither; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Wither; +import org.bukkit.entity.EntityType; + +public class CraftWither extends CraftMonster implements Wither { + public CraftWither(CraftServer server, EntityWither entity) { + super(server, entity); + } + + @Override + public EntityWither getHandle() { + return (EntityWither) entity; + } + + @Override + public String toString() { + return "CraftWither"; + } + + public EntityType getType() { + return EntityType.WITHER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkeleton.java new file mode 100644 index 00000000..0e9939c7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkeleton.java @@ -0,0 +1,28 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityWitherSkeleton; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.WitherSkeleton; + +public class CraftWitherSkeleton extends CraftSkeleton implements WitherSkeleton { + + public CraftWitherSkeleton(CraftServer server, EntityWitherSkeleton entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftWitherSkeleton"; + } + + @Override + public EntityType getType() { + return EntityType.WITHER_SKELETON; + } + + @Override + public SkeletonType getSkeletonType() { + return SkeletonType.WITHER; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java new file mode 100644 index 00000000..7f71b167 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java @@ -0,0 +1,36 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.projectile.EntityWitherSkull; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.WitherSkull; + +public class CraftWitherSkull extends CraftFireball implements WitherSkull { + public CraftWitherSkull(CraftServer server, EntityWitherSkull entity) { + super(server, entity); + } + + @Override + public void setCharged(boolean charged) { + getHandle().setInvulnerable(charged); + } + + @Override + public boolean isCharged() { + return getHandle().isInvulnerable(); + } + + @Override + public EntityWitherSkull getHandle() { + return (EntityWitherSkull) entity; + } + + @Override + public String toString() { + return "CraftWitherSkull"; + } + + public EntityType getType() { + return EntityType.WITHER_SKULL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java new file mode 100644 index 00000000..38a680dd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java @@ -0,0 +1,40 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityWolf; +import net.minecraft.item.EnumDyeColor; +import org.bukkit.DyeColor; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Wolf; + +public class CraftWolf extends CraftTameableAnimal implements Wolf { + public CraftWolf(CraftServer server, EntityWolf wolf) { + super(server, wolf); + } + + public boolean isAngry() { + return getHandle().isAngry(); + } + + public void setAngry(boolean angry) { + getHandle().setAngry(angry); + } + + @Override + public EntityWolf getHandle() { + return (EntityWolf) entity; + } + + @Override + public EntityType getType() { + return EntityType.WOLF; + } + + public DyeColor getCollarColor() { + return DyeColor.getByWoolData((byte) getHandle().getCollarColor().getMetadata()); + } + + public void setCollarColor(DyeColor color) { + getHandle().setCollarColor(EnumDyeColor.byMetadata(color.getWoolData())); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java new file mode 100644 index 00000000..d24ac76a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java @@ -0,0 +1,56 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.monster.EntityZombie; +import net.minecraft.entity.monster.EntityZombieVillager; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Zombie; + +public class CraftZombie extends CraftMonster implements Zombie { + + public CraftZombie(CraftServer server, EntityZombie entity) { + super(server, entity); + } + + @Override + public EntityZombie getHandle() { + return (EntityZombie) entity; + } + + @Override + public String toString() { + return "CraftZombie"; + } + + public EntityType getType() { + return EntityType.ZOMBIE; + } + + public boolean isBaby() { + return getHandle().isChild(); + } + + public void setBaby(boolean flag) { + getHandle().setChild(flag); + } + + public boolean isVillager() { + return getHandle() instanceof EntityZombieVillager; + } + + @Override + public void setVillager(boolean flag) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setVillagerProfession(Villager.Profession profession) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public Villager.Profession getVillagerProfession() { + return null; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombieHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombieHorse.java new file mode 100644 index 00000000..989e7bc6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombieHorse.java @@ -0,0 +1,29 @@ +package org.bukkit.craftbukkit.entity; + +import net.minecraft.entity.passive.EntityZombieHorse; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse.Variant; +import org.bukkit.entity.ZombieHorse; + +public class CraftZombieHorse extends CraftAbstractHorse implements ZombieHorse { + + public CraftZombieHorse(CraftServer server, EntityZombieHorse entity) { + super(server, entity); + } + + @Override + public String toString() { + return "CraftZombieHorse"; + } + + @Override + public EntityType getType() { + return EntityType.ZOMBIE_HORSE; + } + + @Override + public Variant getVariant() { + return Variant.UNDEAD_HORSE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java new file mode 100644 index 00000000..59728a12 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -0,0 +1,1117 @@ +package org.bukkit.craftbukkit.event; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityAreaEffectCloud; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.boss.EntityDragon; +import net.minecraft.entity.item.EntityEnderCrystal; +import net.minecraft.entity.item.EntityFireworkRocket; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.init.Items; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.InventoryCrafting; + +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.client.CPacketCloseWindow; +import net.minecraft.network.play.server.SPacketSetSlot; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EntityDamageSource; +import net.minecraft.util.EntityDamageSourceIndirect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.Explosion; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.Statistic.Type; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftStatistic; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.inventory.CraftMetaBook; +import org.bukkit.craftbukkit.util.CraftDamageSource; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Firework; +import org.bukkit.entity.LightningStrike; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Pig; +import org.bukkit.entity.PigZombie; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.ThrownExpBottle; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.block.*; +import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; +import org.bukkit.event.entity.*; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityDamageEvent.DamageModifier; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.player.*; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.vehicle.VehicleCreateEvent; + +public class CraftEventFactory { + public static final DamageSource MELTING = CraftDamageSource.copyOf(DamageSource.ON_FIRE); + public static final DamageSource POISON = CraftDamageSource.copyOf(DamageSource.MAGIC); + public static Block blockDamage; // For use in EntityDamageByBlockEvent + public static Entity entityDamage; // For use in EntityDamageByEntityEvent + + // helper methods + private static boolean canBuild(CraftWorld world, Player player, int x, int z) { + WorldServer worldServer = world.getHandle(); + int spawnSize = Bukkit.getServer().getSpawnRadius(); + + if (world.getHandle().dimension != 0) return true; + if (spawnSize <= 0) return true; + if (((CraftServer) Bukkit.getServer()).getHandle().getOppedPlayers().isEmpty()) return true; + if (player.isOp()) return true; + + BlockPos chunkcoordinates = worldServer.getSpawnPoint(); + + int distanceFromSpawn = Math.max(Math.abs(x - chunkcoordinates.getX()), Math.abs(z - chunkcoordinates.getZ())); + return distanceFromSpawn > spawnSize; + } + + public static T callEvent(T event) { + Bukkit.getServer().getPluginManager().callEvent(event); + return event; + } + + /** + * Block place methods + */ + public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(World world, EntityPlayer who, EnumHand hand, List blockStates, int clickedX, int clickedY, int clickedZ) { + CraftWorld craftWorld = world.getWorld(); + CraftServer craftServer = world.getServer(); + Player player = (Player) who.getBukkitEntity(); + + Block blockClicked = craftWorld.getBlockAt(clickedX, clickedY, clickedZ); + + boolean canBuild = true; + for (int i = 0; i < blockStates.size(); i++) { + if (!canBuild(craftWorld, player, blockStates.get(i).getX(), blockStates.get(i).getZ())) { + canBuild = false; + break; + } + } + + org.bukkit.inventory.ItemStack item; + if (hand == EnumHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); + } else { + item = player.getInventory().getItemInOffHand(); + } + + BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); + craftServer.getPluginManager().callEvent(event); + + return event; + } + + public static BlockPlaceEvent callBlockPlaceEvent(World world, EntityPlayer who, EnumHand hand, BlockState replacedBlockState, int clickedX, int clickedY, int clickedZ) { + CraftWorld craftWorld = world.getWorld(); + CraftServer craftServer = world.getServer(); + + Player player = (Player) who.getBukkitEntity(); + + Block blockClicked = craftWorld.getBlockAt(clickedX, clickedY, clickedZ); + Block placedBlock = replacedBlockState.getBlock(); + + boolean canBuild = canBuild(craftWorld, player, placedBlock.getX(), placedBlock.getZ()); + + org.bukkit.inventory.ItemStack item; + EquipmentSlot equipmentSlot; + if (hand == EnumHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); + equipmentSlot = EquipmentSlot.HAND; + } else { + item = player.getInventory().getItemInOffHand(); + equipmentSlot = EquipmentSlot.OFF_HAND; + } + + BlockPlaceEvent event = new BlockPlaceEvent(placedBlock, replacedBlockState, blockClicked, item, player, canBuild, equipmentSlot); + craftServer.getPluginManager().callEvent(event); + + return event; + } + + /** + * Bucket methods + */ + public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(EntityPlayer who, int clickedX, int clickedY, int clickedZ, EnumFacing clickedFace, ItemStack itemInHand) { + return (PlayerBucketEmptyEvent) getPlayerBucketEvent(false, who, clickedX, clickedY, clickedZ, clickedFace, itemInHand, Items.BUCKET); + } + + public static PlayerBucketFillEvent callPlayerBucketFillEvent(EntityPlayer who, int clickedX, int clickedY, int clickedZ, EnumFacing clickedFace, ItemStack itemInHand, net.minecraft.item.Item bucket) { + return (PlayerBucketFillEvent) getPlayerBucketEvent(true, who, clickedX, clickedY, clickedZ, clickedFace, itemInHand, bucket); + } + + private static PlayerEvent getPlayerBucketEvent(boolean isFilling, EntityPlayer who, int clickedX, int clickedY, int clickedZ, EnumFacing clickedFace, ItemStack itemstack, net.minecraft.item.Item item) { + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asNewCraftStack(item); + Material bucket = CraftMagicNumbers.getMaterial(itemstack.getItem()); + + CraftWorld craftWorld = (CraftWorld) player.getWorld(); + CraftServer craftServer = (CraftServer) player.getServer(); + + Block blockClicked = craftWorld.getBlockAt(clickedX, clickedY, clickedZ); + BlockFace blockFace = CraftBlock.notchToBlockFace(clickedFace); + + PlayerEvent event = null; + if (isFilling) { + event = new PlayerBucketFillEvent(player, blockClicked, blockFace, bucket, itemInHand); + ((PlayerBucketFillEvent) event).setCancelled(!canBuild(craftWorld, player, clickedX, clickedZ)); + } else { + event = new PlayerBucketEmptyEvent(player, blockClicked, blockFace, bucket, itemInHand); + ((PlayerBucketEmptyEvent) event).setCancelled(!canBuild(craftWorld, player, clickedX, clickedZ)); + } + + craftServer.getPluginManager().callEvent(event); + + return event; + } + + /** + * Player Interact event + */ + public static PlayerInteractEvent callPlayerInteractEvent(EntityPlayer who, Action action, ItemStack itemstack, EnumHand hand) { + if (action != Action.LEFT_CLICK_AIR && action != Action.RIGHT_CLICK_AIR) { + throw new AssertionError(String.format("%s performing %s with %s", who, action, itemstack)); + } + return callPlayerInteractEvent(who, action, null, EnumFacing.SOUTH, itemstack, hand); + } + + public static PlayerInteractEvent callPlayerInteractEvent(EntityPlayer who, Action action, BlockPos position, EnumFacing direction, ItemStack itemstack, EnumHand hand) { + return callPlayerInteractEvent(who, action, position, direction, itemstack, false, hand); + } + + public static PlayerInteractEvent callPlayerInteractEvent(EntityPlayer who, Action action, BlockPos position, EnumFacing direction, ItemStack itemstack, boolean cancelledBlock, EnumHand hand) { + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + + CraftWorld craftWorld = (CraftWorld) player.getWorld(); + CraftServer craftServer = (CraftServer) player.getServer(); + + Block blockClicked = null; + if (position != null) { + blockClicked = craftWorld.getBlockAt(position.getX(), position.getY(), position.getZ()); + } else { + switch (action) { + case LEFT_CLICK_BLOCK: + action = Action.LEFT_CLICK_AIR; + break; + case RIGHT_CLICK_BLOCK: + action = Action.RIGHT_CLICK_AIR; + break; + } + } + BlockFace blockFace = CraftBlock.notchToBlockFace(direction); + + if (itemInHand.getType() == Material.AIR || itemInHand.getAmount() == 0) { + itemInHand = null; + } + + PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } + craftServer.getPluginManager().callEvent(event); + + return event; + } + + /** + * EntityShootBowEvent + */ + public static EntityShootBowEvent callEntityShootBowEvent(EntityLivingBase who, ItemStack itemstack, EntityArrow entityArrow, float force) { + LivingEntity shooter = (LivingEntity) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + Arrow arrow = (Arrow) entityArrow.getBukkitEntity(); + + if (itemInHand != null && (itemInHand.getType() == Material.AIR || itemInHand.getAmount() == 0)) { + itemInHand = null; + } + + EntityShootBowEvent event = new EntityShootBowEvent(shooter, itemInHand, arrow, force); + Bukkit.getPluginManager().callEvent(event); + + return event; + } + + /** + * BlockDamageEvent + */ + public static BlockDamageEvent callBlockDamageEvent(EntityPlayer who, int x, int y, int z, ItemStack itemstack, boolean instaBreak) { + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + + CraftWorld craftWorld = (CraftWorld) player.getWorld(); + CraftServer craftServer = (CraftServer) player.getServer(); + + Block blockClicked = craftWorld.getBlockAt(x, y, z); + + BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, itemInHand, instaBreak); + craftServer.getPluginManager().callEvent(event); + + return event; + } + + /** + * CreatureSpawnEvent + */ + public static CreatureSpawnEvent callCreatureSpawnEvent(EntityLivingBase entityliving, SpawnReason spawnReason) { + LivingEntity entity = (LivingEntity) entityliving.getBukkitEntity(); + CraftServer craftServer = (CraftServer) entity.getServer(); + + CreatureSpawnEvent event = new CreatureSpawnEvent(entity, spawnReason); + craftServer.getPluginManager().callEvent(event); + return event; + } + + /** + * EntityTameEvent + */ + public static EntityTameEvent callEntityTameEvent(EntityLiving entity, EntityPlayer tamer) { + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + org.bukkit.entity.AnimalTamer bukkitTamer = (tamer != null ? tamer.getBukkitEntity() : null); + CraftServer craftServer = (CraftServer) bukkitEntity.getServer(); + + entity.persistenceRequired = true; + + EntityTameEvent event = new EntityTameEvent((LivingEntity) bukkitEntity, bukkitTamer); + craftServer.getPluginManager().callEvent(event); + return event; + } + + /** + * ItemSpawnEvent + */ + public static ItemSpawnEvent callItemSpawnEvent(EntityItem entityitem) { + org.bukkit.entity.Item entity = (org.bukkit.entity.Item) entityitem.getBukkitEntity(); + CraftServer craftServer = (CraftServer) entity.getServer(); + + ItemSpawnEvent event = new ItemSpawnEvent(entity, entity.getLocation()); + + craftServer.getPluginManager().callEvent(event); + return event; + } + + /** + * ItemDespawnEvent + */ + public static ItemDespawnEvent callItemDespawnEvent(EntityItem entityitem) { + org.bukkit.entity.Item entity = (org.bukkit.entity.Item) entityitem.getBukkitEntity(); + + ItemDespawnEvent event = new ItemDespawnEvent(entity, entity.getLocation()); + + entity.getServer().getPluginManager().callEvent(event); + return event; + } + + /** + * ItemMergeEvent + */ + public static ItemMergeEvent callItemMergeEvent(EntityItem merging, EntityItem mergingWith) { + org.bukkit.entity.Item entityMerging = (org.bukkit.entity.Item) merging.getBukkitEntity(); + org.bukkit.entity.Item entityMergingWith = (org.bukkit.entity.Item) mergingWith.getBukkitEntity(); + + ItemMergeEvent event = new ItemMergeEvent(entityMerging, entityMergingWith); + + Bukkit.getPluginManager().callEvent(event); + return event; + } + + /** + * PotionSplashEvent + */ + public static PotionSplashEvent callPotionSplashEvent(EntityPotion potion, Map affectedEntities) { + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + + PotionSplashEvent event = new PotionSplashEvent(thrownPotion, affectedEntities); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(EntityPotion potion, EntityAreaEffectCloud cloud) { + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity(); + + LingeringPotionSplashEvent event = new LingeringPotionSplashEvent(thrownPotion, effectCloud); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + /** + * BlockFadeEvent + */ + public static BlockFadeEvent callBlockFadeEvent(Block block, net.minecraft.block.Block type) { + BlockState state = block.getState(); + state.setTypeId(net.minecraft.block.Block.getIdFromBlock(type)); + + BlockFadeEvent event = new BlockFadeEvent(block, state); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static boolean handleBlockSpreadEvent(Block block, Block source, net.minecraft.block.Block type, int data) { + BlockState state = block.getState(); + state.setTypeId(net.minecraft.block.Block.getIdFromBlock(type)); + state.setRawData((byte) data); + + BlockSpreadEvent event = new BlockSpreadEvent(block, source, state); + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + state.update(true); + } + return !event.isCancelled(); + } + + public static EntityDeathEvent callEntityDeathEvent(EntityLivingBase victim) { + return callEntityDeathEvent(victim, new ArrayList(0)); + } + + public static EntityDeathEvent callEntityDeathEvent(EntityLivingBase victim, List drops) { + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); + // CraftWorld world = (CraftWorld) entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); + + victim.expToDrop = event.getDroppedExp(); + //Kettle start - clear captured drops to allow plugins make changes to them + victim.capturedDrops.clear(); + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + net.minecraft.entity.item.EntityItem entityitem = new net.minecraft.entity.item.EntityItem(victim.world, entity.getLocation().getX(), entity.getLocation().getY(), entity.getLocation().getZ(), CraftItemStack.asNMSCopy(stack)); + // world.dropItemNaturally(entity.getLocation(), stack); we don't want this, spawn items in EntityLivingBase.onDeath() (cauldron stuff) + victim.capturedDrops.add(entityitem); + } + //Kettle end + return event; + } + + public static PlayerDeathEvent callPlayerDeathEvent(EntityPlayerMP victim, List drops, String deathMessage, boolean keepInventory) { + CraftPlayer entity = victim.getBukkitEntity(); + PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); + event.setKeepInventory(keepInventory); + org.bukkit.World world = entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); + + victim.keepLevel = event.getKeepLevel(); + victim.newLevel = event.getNewLevel(); + victim.newTotalExp = event.getNewTotalExp(); + victim.expToDrop = event.getDroppedExp(); + victim.newExp = event.getNewExp(); + + if (event.getKeepInventory()) { + return event; + } + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR) continue; + + world.dropItemNaturally(entity.getLocation(), stack); + } + + return event; + } + + /** + * Server methods + */ + public static ServerListPingEvent callServerListPingEvent(Server craftServer, InetAddress address, String motd, int numPlayers, int maxPlayers) { + ServerListPingEvent event = new ServerListPingEvent(address, motd, numPlayers, maxPlayers); + craftServer.getPluginManager().callEvent(event); + return event; + } + + private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map modifiers, Map> modifierFunctions) { + if (source.isExplosion()) { + DamageCause damageCause; + Entity damager = entityDamage; + entityDamage = null; + EntityDamageEvent event; + if (damager == null) { + event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.BLOCK_EXPLOSION, modifiers, modifierFunctions); + } else if (entity instanceof EntityDragon && /*PAIL FIXME ((EntityEnderDragon) entity).target == damager*/ false) { + event = new EntityDamageEvent(entity.getBukkitEntity(), DamageCause.ENTITY_EXPLOSION, modifiers, modifierFunctions); + } else { + if (damager instanceof org.bukkit.entity.TNTPrimed) { + damageCause = DamageCause.BLOCK_EXPLOSION; + } else { + damageCause = DamageCause.ENTITY_EXPLOSION; + } + event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions); + } + + callEvent(event); + + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + return event; + } else if (source instanceof EntityDamageSource) { + Entity damager = source.getTrueSource(); + DamageCause cause = (source.isSweep()) ? DamageCause.ENTITY_SWEEP_ATTACK : DamageCause.ENTITY_ATTACK; + + if (source instanceof EntityDamageSourceIndirect) { + damager = ((EntityDamageSourceIndirect) source).getProximateDamageSource(); + if (damager.getBukkitEntity() instanceof ThrownPotion) { + cause = DamageCause.MAGIC; + } else if (damager.getBukkitEntity() instanceof Projectile) { + cause = DamageCause.PROJECTILE; + } + } else if ("thorns".equals(source.damageType)) { + cause = DamageCause.THORNS; + } + + return callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions); + } else if (source == DamageSource.OUT_OF_WORLD) { + EntityDamageEvent event = callEvent(new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.VOID, modifiers, modifierFunctions)); + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + return event; + } else if (source == DamageSource.LAVA) { + EntityDamageEvent event = callEvent(new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.LAVA, modifiers, modifierFunctions)); + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + return event; + } else if (blockDamage != null) { + DamageCause cause; + Block damager = blockDamage; + blockDamage = null; + if (source == DamageSource.CACTUS) { + cause = DamageCause.CONTACT; + } else if (source == DamageSource.HOT_FLOOR) { + cause = DamageCause.HOT_FLOOR; + } else { + cause = DamageCause.CUSTOM; + } + EntityDamageEvent event = callEvent(new EntityDamageByBlockEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions)); + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + return event; + } else if (entityDamage != null) { + DamageCause cause; + CraftEntity damager = entityDamage.getBukkitEntity(); + entityDamage = null; + if (source == DamageSource.ANVIL || source == DamageSource.FALLING_BLOCK) { + cause = DamageCause.FALLING_BLOCK; + } else if (damager instanceof LightningStrike) { + cause = DamageCause.LIGHTNING; + } else if (source == DamageSource.FALL) { + cause = DamageCause.FALL; + } else if (source == DamageSource.DRAGON_BREATH) { + cause = DamageCause.DRAGON_BREATH; + } else if (source == DamageSource.MAGIC) { + cause = DamageCause.MAGIC; + } else { + cause = DamageCause.CUSTOM; + } + EntityDamageEvent event = callEvent(new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions)); + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + return event; + } + + DamageCause cause; + if (source == DamageSource.IN_FIRE) { + cause = DamageCause.FIRE; + } else if (source == DamageSource.STARVE) { + cause = DamageCause.STARVATION; + } else if (source == DamageSource.WITHER) { + cause = DamageCause.WITHER; + } else if (source == DamageSource.IN_WALL) { + cause = DamageCause.SUFFOCATION; + } else if (source == DamageSource.DROWN) { + cause = DamageCause.DROWNING; + } else if (source == DamageSource.ON_FIRE) { + cause = DamageCause.FIRE_TICK; + } else if (source == MELTING) { + cause = DamageCause.MELTING; + } else if (source == POISON) { + cause = DamageCause.POISON; + } else if (source == DamageSource.MAGIC) { + cause = DamageCause.MAGIC; + } else if (source == DamageSource.FALL) { + cause = DamageCause.FALL; + } else if (source == DamageSource.FLY_INTO_WALL) { + cause = DamageCause.FLY_INTO_WALL; + } else if (source == DamageSource.CRAMMING) { + cause = DamageCause.CRAMMING; + } else if (source == DamageSource.GENERIC) { + cause = DamageCause.CUSTOM; + } else { + cause = DamageCause.CUSTOM; + } + + return callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions); + } + + private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions) { + EntityDamageEvent event; + if (damager != null) { + event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); + } + + callEvent(event); + + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } + + return event; + } + + private static final Function ZERO = Functions.constant(-0.0); + + public static EntityDamageEvent handleLivingEntityDamageEvent(Entity damagee, DamageSource source, double rawDamage, double hardHatModifier, double blockingModifier, double armorModifier, double resistanceModifier, double magicModifier, double absorptionModifier, Function hardHat, Function blocking, Function armor, Function resistance, Function magic, Function absorption) { + Map modifiers = new EnumMap(DamageModifier.class); + Map> modifierFunctions = new EnumMap>(DamageModifier.class); + modifiers.put(DamageModifier.BASE, rawDamage); + modifierFunctions.put(DamageModifier.BASE, ZERO); + if (source == DamageSource.FALLING_BLOCK || source == DamageSource.ANVIL) { + modifiers.put(DamageModifier.HARD_HAT, hardHatModifier); + modifierFunctions.put(DamageModifier.HARD_HAT, hardHat); + } + if (damagee instanceof EntityPlayer) { + modifiers.put(DamageModifier.BLOCKING, blockingModifier); + modifierFunctions.put(DamageModifier.BLOCKING, blocking); + } + modifiers.put(DamageModifier.ARMOR, armorModifier); + modifierFunctions.put(DamageModifier.ARMOR, armor); + modifiers.put(DamageModifier.RESISTANCE, resistanceModifier); + modifierFunctions.put(DamageModifier.RESISTANCE, resistance); + modifiers.put(DamageModifier.MAGIC, magicModifier); + modifierFunctions.put(DamageModifier.MAGIC, magic); + modifiers.put(DamageModifier.ABSORPTION, absorptionModifier); + modifierFunctions.put(DamageModifier.ABSORPTION, absorption); + return handleEntityDamageEvent(damagee, source, modifiers, modifierFunctions); + } + + // Non-Living Entities such as EntityEnderCrystal and EntityFireball need to call this + public static boolean handleNonLivingEntityDamageEvent(Entity entity, DamageSource source, double damage) { + return handleNonLivingEntityDamageEvent(entity, source, damage, true); + } + + public static boolean handleNonLivingEntityDamageEvent(Entity entity, DamageSource source, double damage, boolean cancelOnZeroDamage) { + if (entity instanceof EntityEnderCrystal && !(source instanceof EntityDamageSource)) { + return false; + } + + final EnumMap modifiers = new EnumMap(DamageModifier.class); + final EnumMap> functions = new EnumMap(DamageModifier.class); + + modifiers.put(DamageModifier.BASE, damage); + functions.put(DamageModifier.BASE, ZERO); + + final EntityDamageEvent event = handleEntityDamageEvent(entity, source, modifiers, functions); + if (event == null) { + return false; + } + return event.isCancelled() || (cancelOnZeroDamage && event.getDamage() == 0); + } + + public static PlayerLevelChangeEvent callPlayerLevelChangeEvent(Player player, int oldLevel, int newLevel) { + PlayerLevelChangeEvent event = new PlayerLevelChangeEvent(player, oldLevel, newLevel); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static PlayerExpChangeEvent callPlayerExpChangeEvent(EntityPlayer entity, int expAmount) { + Player player = (Player) entity.getBukkitEntity(); + PlayerExpChangeEvent event = new PlayerExpChangeEvent(player, expAmount); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static PlayerItemMendEvent callPlayerItemMendEvent(EntityPlayer entity, EntityXPOrb orb, ItemStack nmsMendedItem, int repairAmount) { + Player player = (Player) entity.getBukkitEntity(); + org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asCraftMirror(nmsMendedItem); + PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, (ExperienceOrb) orb.getBukkitEntity(), repairAmount); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static boolean handleBlockGrowEvent(World world, int x, int y, int z, net.minecraft.block.Block type, int data) { + Block block = world.getWorld().getBlockAt(x, y, z); + CraftBlockState state = (CraftBlockState) block.getState(); + state.setTypeId(net.minecraft.block.Block.getIdFromBlock(type)); + state.setRawData((byte) data); + + BlockGrowEvent event = new BlockGrowEvent(block, state); + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + state.update(true); + } + + return !event.isCancelled(); + } + + public static FoodLevelChangeEvent callFoodLevelChangeEvent(EntityPlayer entity, int level) { + FoodLevelChangeEvent event = new FoodLevelChangeEvent(entity.getBukkitEntity(), level); + entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static PigZapEvent callPigZapEvent(Entity pig, Entity lightning, Entity pigzombie) { + PigZapEvent event = new PigZapEvent((Pig) pig.getBukkitEntity(), (LightningStrike) lightning.getBukkitEntity(), (PigZombie) pigzombie.getBukkitEntity()); + pig.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static HorseJumpEvent callHorseJumpEvent(Entity horse, float power) { + HorseJumpEvent event = new HorseJumpEvent((AbstractHorse) horse.getBukkitEntity(), power); + horse.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(org.bukkit.entity.Entity entity, Block block, Material material) { + return callEntityChangeBlockEvent(entity, block, material, 0); + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(Entity entity, Block block, Material material) { + return callEntityChangeBlockEvent(entity.getBukkitEntity(), block, material, 0); + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(Entity entity, Block block, Material material, boolean cancelled) { + return callEntityChangeBlockEvent(entity.getBukkitEntity(), block, material, 0, cancelled); + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(Entity entity, BlockPos position, net.minecraft.block.Block type, int data) { + Block block = entity.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); + Material material = CraftMagicNumbers.getMaterial(type); + + return callEntityChangeBlockEvent(entity.getBukkitEntity(), block, material, data); + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(org.bukkit.entity.Entity entity, Block block, Material material, int data) { + return callEntityChangeBlockEvent(entity, block, material, data, false); + } + + public static EntityChangeBlockEvent callEntityChangeBlockEvent(org.bukkit.entity.Entity entity, Block block, Material material, int data, boolean cancelled) { + EntityChangeBlockEvent event = new EntityChangeBlockEvent(entity, block, material, (byte) data); + event.setCancelled(cancelled); + entity.getServer().getPluginManager().callEvent(event); + return event; + } + + public static CreeperPowerEvent callCreeperPowerEvent(Entity creeper, Entity lightning, CreeperPowerEvent.PowerCause cause) { + CreeperPowerEvent event = new CreeperPowerEvent((Creeper) creeper.getBukkitEntity(), (LightningStrike) lightning.getBukkitEntity(), cause); + creeper.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static EntityTargetEvent callEntityTargetEvent(Entity entity, Entity target, EntityTargetEvent.TargetReason reason) { + EntityTargetEvent event = new EntityTargetEvent(entity.getBukkitEntity(), target == null ? null : target.getBukkitEntity(), reason); + entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static EntityTargetLivingEntityEvent callEntityTargetLivingEvent(Entity entity, EntityLivingBase target, EntityTargetEvent.TargetReason reason) { + EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(entity.getBukkitEntity(), (LivingEntity) target.getBukkitEntity(), reason); + entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); + return event; + } + + public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, int x, int y, int z) { + org.bukkit.entity.Entity entity1 = entity.getBukkitEntity(); + Block block = entity1.getWorld().getBlockAt(x, y, z); + + EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block); + entity1.getServer().getPluginManager().callEvent(event); + + return event; + } + + public static Container callInventoryOpenEvent(EntityPlayerMP player, Container container) { + return callInventoryOpenEvent(player, container, false); + } + + public static Container callInventoryOpenEvent(EntityPlayerMP player, Container container, boolean cancelled) { + if (player.openContainer != player.inventoryContainer) { // fire INVENTORY_CLOSE if one already open + player.connection.processCloseWindow(new CPacketCloseWindow(player.openContainer.windowId)); + } + + CraftServer server = player.world.getServer(); + CraftPlayer craftPlayer = player.getBukkitEntity(); + player.openContainer.transferTo(container, craftPlayer); + + InventoryOpenEvent event = new InventoryOpenEvent(container.getBukkitView()); + event.setCancelled(cancelled); + server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + container.transferTo(player.openContainer, craftPlayer); + return null; + } + + return container; + } + + public static ItemStack callPreCraftEvent(InventoryCrafting matrix, ItemStack result, InventoryView lastCraftView, boolean isRepair) { + CraftInventoryCrafting inventory = new CraftInventoryCrafting(matrix, matrix.resultInventory); + inventory.setResult(CraftItemStack.asCraftMirror(result)); + + PrepareItemCraftEvent event = new PrepareItemCraftEvent(inventory, lastCraftView, isRepair); + Bukkit.getPluginManager().callEvent(event); + + org.bukkit.inventory.ItemStack bitem = event.getInventory().getResult(); + + return CraftItemStack.asNMSCopy(bitem); + } + + public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { + Projectile bukkitEntity = (Projectile) entity.getBukkitEntity(); + ProjectileLaunchEvent event = new ProjectileLaunchEvent(bukkitEntity); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static ProjectileHitEvent callProjectileHitEvent(Entity entity, RayTraceResult position) { + Block hitBlock = null; + if (position.typeOfHit == RayTraceResult.Type.BLOCK) { + BlockPos blockposition = position.getBlockPos(); + hitBlock = entity.getBukkitEntity().getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + } + + ProjectileHitEvent event = new ProjectileHitEvent((Projectile) entity.getBukkitEntity(), position.entityHit == null ? null : position.entityHit.getBukkitEntity(), hitBlock); + entity.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static ExpBottleEvent callExpBottleEvent(Entity entity, int exp) { + ThrownExpBottle bottle = (ThrownExpBottle) entity.getBukkitEntity(); + ExpBottleEvent event = new ExpBottleEvent(bottle, exp); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static BlockRedstoneEvent callRedstoneChange(World world, int x, int y, int z, int oldCurrent, int newCurrent) { + BlockRedstoneEvent event = new BlockRedstoneEvent(world.getWorld().getBlockAt(x, y, z), oldCurrent, newCurrent); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static NotePlayEvent callNotePlayEvent(World world, int x, int y, int z, byte instrument, byte note) { + NotePlayEvent event = new NotePlayEvent(world.getWorld().getBlockAt(x, y, z), org.bukkit.Instrument.getByType(instrument), new org.bukkit.Note(note)); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static void callPlayerItemBreakEvent(EntityPlayer human, ItemStack brokenItem) { + CraftItemStack item = CraftItemStack.asCraftMirror(brokenItem); + PlayerItemBreakEvent event = new PlayerItemBreakEvent((Player) human.getBukkitEntity(), item); + Bukkit.getPluginManager().callEvent(event); + } + + public static BlockIgniteEvent callBlockIgniteEvent(World world, int x, int y, int z, int igniterX, int igniterY, int igniterZ) { + org.bukkit.World bukkitWorld = world.getWorld(); + Block igniter = bukkitWorld.getBlockAt(igniterX, igniterY, igniterZ); + IgniteCause cause; + switch (igniter.getType()) { + case LAVA: + case STATIONARY_LAVA: + cause = IgniteCause.LAVA; + break; + case DISPENSER: + cause = IgniteCause.FLINT_AND_STEEL; + break; + case FIRE: // Fire or any other unknown block counts as SPREAD. + default: + cause = IgniteCause.SPREAD; + } + + BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(x, y, z), cause, igniter); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static BlockIgniteEvent callBlockIgniteEvent(World world, int x, int y, int z, Entity igniter) { + org.bukkit.World bukkitWorld = world.getWorld(); + org.bukkit.entity.Entity bukkitIgniter = igniter.getBukkitEntity(); + IgniteCause cause; + switch (bukkitIgniter.getType()) { + case ENDER_CRYSTAL: + cause = IgniteCause.ENDER_CRYSTAL; + break; + case LIGHTNING: + cause = IgniteCause.LIGHTNING; + break; + case SMALL_FIREBALL: + case FIREBALL: + cause = IgniteCause.FIREBALL; + break; + default: + cause = IgniteCause.FLINT_AND_STEEL; + } + + BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(x, y, z), cause, bukkitIgniter); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static BlockIgniteEvent callBlockIgniteEvent(World world, int x, int y, int z, Explosion explosion) { + org.bukkit.World bukkitWorld = world.getWorld(); + org.bukkit.entity.Entity igniter = explosion.exploder == null ? null : explosion.exploder.getBukkitEntity(); + + BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(x, y, z), IgniteCause.EXPLOSION, igniter); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static BlockIgniteEvent callBlockIgniteEvent(World world, int x, int y, int z, IgniteCause cause, Entity igniter) { + BlockIgniteEvent event = new BlockIgniteEvent(world.getWorld().getBlockAt(x, y, z), cause, igniter.getBukkitEntity()); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static void handleInventoryCloseEvent(EntityPlayer human) { + InventoryCloseEvent event = new InventoryCloseEvent(human.openContainer.getBukkitView()); + human.world.getServer().getPluginManager().callEvent(event); + human.openContainer.transferTo(human.inventoryContainer, human.getBukkitEntity()); + } + + public static void handleEditBookEvent(EntityPlayerMP player, ItemStack newBookItem) { + int itemInHandIndex = player.inventory.currentItem; + + PlayerEditBookEvent editBookEvent = new PlayerEditBookEvent(player.getBukkitEntity(), player.inventory.currentItem, (BookMeta) CraftItemStack.getItemMeta(player.inventory.getCurrentItem()), (BookMeta) CraftItemStack.getItemMeta(newBookItem), newBookItem.getItem() == Items.WRITTEN_BOOK); + player.world.getServer().getPluginManager().callEvent(editBookEvent); + ItemStack itemInHand = player.inventory.getStackInSlot(itemInHandIndex); + + // If they've got the same item in their hand, it'll need to be updated. + if (itemInHand != null && itemInHand.getItem() == Items.WRITABLE_BOOK) { + if (!editBookEvent.isCancelled()) { + if (editBookEvent.isSigning()) { + itemInHand.setItem(Items.WRITTEN_BOOK); + } + CraftMetaBook meta = (CraftMetaBook) editBookEvent.getNewBookMeta(); + List pages = meta.pages; + for (int i = 0; i < pages.size(); i++) { + pages.set(i, stripEvents(pages.get(i))); + } + CraftItemStack.setItemMeta(itemInHand, meta); + } + + // Client will have updated its idea of the book item; we need to overwrite that + Slot slot = player.openContainer.getSlotFromInventory(player.inventory, itemInHandIndex); + player.connection.sendPacket(new SPacketSetSlot(player.openContainer.windowId, slot.slotNumber, itemInHand)); + } + } + + private static ITextComponent stripEvents(ITextComponent c) { + Style modi = c.getStyle(); + if (modi != null) { + modi.setClickEvent(null); + modi.setHoverEvent(null); + } + c.setStyle(modi); + if (c instanceof TextComponentTranslation) { + TextComponentTranslation cm = (TextComponentTranslation) c; + Object[] oo = cm.getFormatArgs(); + for (int i = 0; i < oo.length; i++) { + Object o = oo[i]; + if (o instanceof ITextComponent) { + oo[i] = stripEvents((ITextComponent) o); + } + } + } + List ls = c.getSiblings(); + if (ls != null) { + for (int i = 0; i < ls.size(); i++) { + ls.set(i, stripEvents(ls.get(i))); + } + } + return c; + } + + public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(EntityLiving entity, EntityPlayer player) { + PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity()); + entity.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static PlayerLeashEntityEvent callPlayerLeashEntityEvent(EntityLiving entity, Entity leashHolder, EntityPlayer player) { + PlayerLeashEntityEvent event = new PlayerLeashEntityEvent(entity.getBukkitEntity(), leashHolder.getBukkitEntity(), (Player) player.getBukkitEntity()); + entity.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static Cancellable handleStatisticsIncrease(EntityPlayer entityHuman, net.minecraft.stats.StatBase statistic, int current, int incrementation) { + Player player = ((EntityPlayerMP) entityHuman).getBukkitEntity(); + Event event; + if (true) { + org.bukkit.Statistic stat = CraftStatistic.getBukkitStatistic(statistic); + if (stat == null) { + // System.err.println("Unhandled statistic: " + statistic); + return null; + } + switch (stat) { + case FALL_ONE_CM: + case BOAT_ONE_CM: + case CLIMB_ONE_CM: + case DIVE_ONE_CM: + case FLY_ONE_CM: + case HORSE_ONE_CM: + case MINECART_ONE_CM: + case PIG_ONE_CM: + case PLAY_ONE_TICK: + case SWIM_ONE_CM: + case WALK_ONE_CM: + case SPRINT_ONE_CM: + case CROUCH_ONE_CM: + case TIME_SINCE_DEATH: + case SNEAK_TIME: + // Do not process event for these - too spammy + return null; + default: + } + if (stat.getType() == Type.UNTYPED) { + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation); + } else if (stat.getType() == Type.ENTITY) { + EntityType entityType = CraftStatistic.getEntityTypeFromStatistic(statistic); + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation, entityType); + } else { + Material material = CraftStatistic.getMaterialFromStatistic(statistic); + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation, material); + } + } + entityHuman.world.getServer().getPluginManager().callEvent(event); + return (Cancellable) event; + } + + public static FireworkExplodeEvent callFireworkExplodeEvent(EntityFireworkRocket firework) { + FireworkExplodeEvent event = new FireworkExplodeEvent((Firework) firework.getBukkitEntity()); + firework.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) { + PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setItem(2, event.getResult()); + return event; + } + + /** + * Mob spawner event. + */ + public static SpawnerSpawnEvent callSpawnerSpawnEvent(Entity spawnee, BlockPos pos) { + CraftEntity entity = spawnee.getBukkitEntity(); + BlockState state = entity.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); + if (!(state instanceof org.bukkit.block.CreatureSpawner)) { + state = null; + } + + SpawnerSpawnEvent event = new SpawnerSpawnEvent(entity, (org.bukkit.block.CreatureSpawner) state); + entity.getServer().getPluginManager().callEvent(event); + return event; + } + + public static EntityToggleGlideEvent callToggleGlideEvent(EntityLivingBase entity, boolean gliding) { + EntityToggleGlideEvent event = new EntityToggleGlideEvent((LivingEntity) entity.getBukkitEntity(), gliding); + entity.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static AreaEffectCloudApplyEvent callAreaEffectCloudApplyEvent(EntityAreaEffectCloud cloud, List entities) { + AreaEffectCloudApplyEvent event = new AreaEffectCloudApplyEvent((AreaEffectCloud) cloud.getBukkitEntity(), entities); + cloud.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static VehicleCreateEvent callVehicleCreateEvent(Entity entity) { + Vehicle bukkitEntity = (Vehicle) entity.getBukkitEntity(); + VehicleCreateEvent event = new VehicleCreateEvent(bukkitEntity); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static EntityBreedEvent callEntityBreedEvent(EntityLivingBase child, EntityLivingBase mother, EntityLivingBase father, EntityLivingBase breeder, ItemStack bredWith, int experience) { + LivingEntity breederEntity = (LivingEntity) (breeder == null ? null : breeder.getBukkitEntity()); + CraftItemStack bredWithStack = bredWith == null ? null : CraftItemStack.asCraftMirror(bredWith).clone(); + + EntityBreedEvent event = new EntityBreedEvent((LivingEntity) child.getBukkitEntity(), (LivingEntity) mother.getBukkitEntity(), (LivingEntity) father.getBukkitEntity(), breederEntity, bredWithStack, experience); + child.world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static BlockPhysicsEvent callBlockPhysicsEvent(World world, BlockPos blockposition) { + Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + BlockPhysicsEvent event = new BlockPhysicsEvent(block, block.getTypeId()); + world.getServer().getPluginManager().callEvent(event); + return event; + } + + public static boolean handleBlockFormEvent(World world, BlockPos pos, IBlockState block, @Nullable Entity entity) { + BlockState blockState = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); + blockState.setType(CraftMagicNumbers.getMaterial(block.getBlock())); + blockState.setRawData((byte) block.getBlock().getMetaFromState(block)); + + BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState); + world.getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + blockState.update(true); + } + + return !event.isCancelled(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java new file mode 100644 index 00000000..01264a7e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java @@ -0,0 +1,199 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.bukkit.craftbukkit.generator; + +import java.util.Arrays; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.material.MaterialData; + +/** + * Data to be used for the block types and data in a newly generated chunk. + */ +public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; + private final char[][] sections; + + public CraftChunkData(World world) { + this(world.getMaxHeight()); + } + + /* pp for tests */ CraftChunkData(int maxHeight) { + if (maxHeight > 256) { + throw new IllegalArgumentException("World height exceeded max chunk height"); + } + this.maxHeight = maxHeight; + // Minecraft hardcodes this to 16 chunk sections. + sections = new char[16][]; + } + + @Override + public int getMaxHeight() { + return maxHeight; + } + + @Override + public void setBlock(int x, int y, int z, Material material) { + setBlock(x, y, z, material.getId()); + } + + @Override + public void setBlock(int x, int y, int z, MaterialData material) { + setBlock(x, y, z, material.getItemTypeId(), material.getData()); + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) { + setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.getId()); + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) { + setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.getItemTypeId(), material.getData()); + } + + @Override + public Material getType(int x, int y, int z) { + return Material.getMaterial(getTypeId(x, y, z)); + } + + @Override + public MaterialData getTypeAndData(int x, int y, int z) { + return getType(x, y, z).getNewData(getData(x, y, z)); + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, int blockId) { + setRegion(xMin, yMin, zMin, xMax, yMax, zMax, blockId, (byte) 0); + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, int blockId, int data) { + // Clamp to sane values. + if (xMin > 0xf || yMin >= maxHeight || zMin > 0xf) { + return; + } + if (xMin < 0) { + xMin = 0; + } + if (yMin < 0) { + yMin = 0; + } + if (zMin < 0) { + zMin = 0; + } + if (xMax > 0x10) { + xMax = 0x10; + } + if (yMax > maxHeight) { + yMax = maxHeight; + } + if (zMax > 0x10) { + zMax = 0x10; + } + if (xMin >= xMax || yMin >= yMax || zMin >= zMax) { + return; + } + char typeChar = (char) ((blockId << 4) | data); + if (xMin == 0 && xMax == 0x10) { + if (zMin == 0 && zMax == 0x10) { + for (int y = yMin & 0xf0; y < yMax; y += 0x10) { + char[] section = getChunkSection(y, true); + if (y <= yMin) { + if (y + 0x10 > yMax) { + // First and last chunk section + Arrays.fill(section, (yMin & 0xf) << 8, (yMax & 0xf) << 8, typeChar); + } else { + // First chunk section + Arrays.fill(section, (yMin & 0xf) << 8, 0x1000, typeChar); + } + } else if (y + 0x10 > yMax) { + // Last chunk section + Arrays.fill(section, 0, (yMax & 0xf) << 8, typeChar); + } else { + // Full chunk section + Arrays.fill(section, 0, 0x1000, typeChar); + } + } + } else { + for (int y = yMin; y < yMax; y++) { + char[] section = getChunkSection(y, true); + int offsetBase = (y & 0xf) << 8; + int min = offsetBase | (zMin << 4); + // Need to add zMax as it can be 16, which overlaps the y coordinate bits + int max = offsetBase + (zMax << 4); + Arrays.fill(section, min, max, typeChar); + } + } + } else { + for (int y = yMin; y < yMax; y++) { + char[] section = getChunkSection(y, true); + int offsetBase = (y & 0xf) << 8; + for (int z = zMin; z < zMax; z++) { + int offset = offsetBase | (z << 4); + // Need to add xMax as it can be 16, which overlaps the z coordinate bits + Arrays.fill(section, offset | xMin, offset + xMax, typeChar); + } + } + } + } + + @Override + public void setBlock(int x, int y, int z, int blockId) { + setBlock(x, y, z, blockId, (byte) 0); + } + + @Override + public void setBlock(int x, int y, int z, int blockId, byte data) { + setBlock(x, y, z, (char) (blockId << 4 | data)); + } + + @Override + public int getTypeId(int x, int y, int z) { + if (x != (x & 0xf) || y < 0 || y >= maxHeight || z != (z & 0xf)) { + return 0; + } + char[] section = getChunkSection(y, false); + if (section == null) { + return 0; + } else { + return section[(y & 0xf) << 8 | z << 4 | x] >> 4; + } + } + + @Override + public byte getData(int x, int y, int z) { + if (x != (x & 0xf) || y < 0 || y >= maxHeight || z != (z & 0xf)) { + return (byte) 0; + } + char[] section = getChunkSection(y, false); + if (section == null) { + return (byte) 0; + } else { + return (byte) (section[(y & 0xf) << 8 | z << 4 | x] & 0xf); + } + } + + private void setBlock(int x, int y, int z, char type) { + if (x != (x & 0xf) || y < 0 || y >= maxHeight || z != (z & 0xf)) { + return; + } + char[] section = getChunkSection(y, true); + section[(y & 0xf) << 8 | z << 4 | x] = type; + } + + private char[] getChunkSection(int y, boolean create) { + char[] section = sections[y >> 4]; + if (create && section == null) { + sections[y >> 4] = section = new char[0x1000]; + } + return section; + } + + char[][] getRawChunkData() { + return sections; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java new file mode 100644 index 00000000..d231d344 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java @@ -0,0 +1,247 @@ +package org.bukkit.craftbukkit.generator; + +import java.util.List; +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.entity.EnumCreatureType; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import net.minecraft.world.gen.structure.MapGenStronghold; +import org.bukkit.block.Biome; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.craftbukkit.block.CraftBlock; + +public class CustomChunkGenerator extends InternalChunkGenerator { + private final ChunkGenerator generator; + private final WorldServer world; + private final Random random; + private final MapGenStronghold strongholdGen = new MapGenStronghold(); + + private static class CustomBiomeGrid implements BiomeGrid { + net.minecraft.world.biome.Biome[] biome; + + @Override + public Biome getBiome(int x, int z) { + return CraftBlock.biomeBaseToBiome(biome[(z << 4) | x]); + } + + @Override + public void setBiome(int x, int z, Biome bio) { + biome[(z << 4) | x] = CraftBlock.biomeToBiomeBase(bio); + } + } + + public CustomChunkGenerator(World world, long seed, ChunkGenerator generator) { + this.world = (WorldServer) world; + this.generator = generator; + + this.random = new Random(seed); + } + + @Override + public Chunk generateChunk(int x, int z) { + random.setSeed((long) x * 341873128712L + (long) z * 132897987541L); + + Chunk chunk; + + // Get default biome data for chunk + CustomBiomeGrid biomegrid = new CustomBiomeGrid(); + biomegrid.biome = new net.minecraft.world.biome.Biome[256]; + world.getBiomeProvider().getBiomes(biomegrid.biome, x << 4, z << 4, 16, 16); + + // Try ChunkData method (1.8+) + CraftChunkData data = (CraftChunkData) generator.generateChunkData(this.world.getWorld(), random, x, z, biomegrid); + if (data != null) { + char[][] sections = data.getRawChunkData(); + chunk = new Chunk(this.world, x, z); + + ExtendedBlockStorage[] csect = chunk.getBlockStorageArray(); + int scnt = Math.min(csect.length, sections.length); + + // Loop through returned sections + for (int sec = 0; sec < scnt; sec++) { + if(sections[sec] == null) { + continue; + } + char[] section = sections[sec]; + char emptyTest = 0; + for (int i = 0; i < 4096; i++) { + // Filter invalid block id & data values. + if (Block.BLOCK_STATE_IDS.getByValue(section[i]) == null) { + section[i] = 0; + } + emptyTest |= section[i]; + } + // Build chunk section + if (emptyTest != 0) { + csect[sec] = new ExtendedBlockStorage(sec << 4, true, section); + } + } + } + else { + // Try extended block method (1.2+) + short[][] xbtypes = generator.generateExtBlockSections(this.world.getWorld(), this.random, x, z, biomegrid); + if (xbtypes != null) { + chunk = new Chunk(this.world, x, z); + + ExtendedBlockStorage[] csect = chunk.getBlockStorageArray(); + int scnt = Math.min(csect.length, xbtypes.length); + + // Loop through returned sections + for (int sec = 0; sec < scnt; sec++) { + if (xbtypes[sec] == null) { + continue; + } + char[] secBlkID = new char[4096]; // Allocate blk ID bytes + short[] bdata = xbtypes[sec]; + for (int i = 0; i < bdata.length; i++) { + Block b = Block.getBlockById(bdata[i]); + secBlkID[i] = (char) Block.BLOCK_STATE_IDS.get(b.getDefaultState()); + } + // Build chunk section + csect[sec] = new ExtendedBlockStorage(sec << 4, true, secBlkID); + } + } + else { // Else check for byte-per-block section data + byte[][] btypes = generator.generateBlockSections(this.world.getWorld(), this.random, x, z, biomegrid); + + if (btypes != null) { + chunk = new Chunk(this.world, x, z); + + ExtendedBlockStorage[] csect = chunk.getBlockStorageArray(); + int scnt = Math.min(csect.length, btypes.length); + + for (int sec = 0; sec < scnt; sec++) { + if (btypes[sec] == null) { + continue; + } + + char[] secBlkID = new char[4096]; // Allocate block ID bytes + for (int i = 0; i < secBlkID.length; i++) { + Block b = Block.getBlockById(btypes[sec][i] & 0xFF); + secBlkID[i] = (char) Block.BLOCK_STATE_IDS.get(b.getDefaultState()); + } + csect[sec] = new ExtendedBlockStorage(sec << 4, true, secBlkID); + } + } + else { // Else, fall back to pre 1.2 method + @SuppressWarnings("deprecation") + byte[] types = generator.generate(this.world.getWorld(), this.random, x, z); + int ydim = types.length / 256; + int scnt = ydim / 16; + + chunk = new Chunk(this.world, x, z); // Create empty chunk + + ExtendedBlockStorage[] csect = chunk.getBlockStorageArray(); + + scnt = Math.min(scnt, csect.length); + // Loop through sections + for (int sec = 0; sec < scnt; sec++) { + char[] csbytes = null; // Add sections when needed + + for (int cy = 0; cy < 16; cy++) { + int cyoff = cy | (sec << 4); + + for (int cx = 0; cx < 16; cx++) { + int cxyoff = (cx * ydim * 16) + cyoff; + + for (int cz = 0; cz < 16; cz++) { + byte blk = types[cxyoff + (cz * ydim)]; + + if (blk != 0) { // If non-empty + if (csbytes == null) { // If no section yet, get one + csbytes = new char[16*16*16]; + } + + Block b = Block.getBlockById(blk & 0xFF); + csbytes[(cy << 8) | (cz << 4) | cx] = (char) Block.BLOCK_STATE_IDS.get(b.getDefaultState()); + } + } + } + } + // If section built, finish prepping its state + if (csbytes != null) { + ExtendedBlockStorage cs = csect[sec] = new ExtendedBlockStorage(sec << 4, true, csbytes); + cs.recalculateRefCounts(); + } + } + } + } + } + // Set biome grid + byte[] biomeIndex = chunk.getBiomeArray(); + for (int i = 0; i < biomeIndex.length; i++) { + biomeIndex[i] = (byte) (net.minecraft.world.biome.Biome.REGISTRY.getIDForObject(biomegrid.biome[i]) & 0xFF); // PAIL : rename + } + // Initialize lighting + chunk.generateSkylightMap(); + + return chunk; + } + + @Override + public boolean generateStructures(Chunk chunk, int i, int i1) { + return false; + } + + @SuppressWarnings("deprecation") + @Override + public byte[] generate(org.bukkit.World world, Random random, int x, int z) { + return generator.generate(world, random, x, z); + } + + @Override + public byte[][] generateBlockSections(org.bukkit.World world, Random random, int x, int z, BiomeGrid biomes) { + return generator.generateBlockSections(world, random, x, z, biomes); + } + + @Override + public short[][] generateExtBlockSections(org.bukkit.World world, Random random, int x, int z, BiomeGrid biomes) { + return generator.generateExtBlockSections(world, random, x, z, biomes); + } + + public Chunk getChunkAt(int x, int z) { + return generateChunk(x, z); + } + + @Override + public boolean canSpawn(org.bukkit.World world, int x, int z) { + return generator.canSpawn(world, x, z); + } + + @Override + public List getDefaultPopulators(org.bukkit.World world) { + return generator.getDefaultPopulators(world); + } + + @Override + public List getPossibleCreatures(EnumCreatureType type, BlockPos position) { + net.minecraft.world.biome.Biome biomebase = world.getBiome(position); + + return biomebase == null ? null : biomebase.getSpawnableList(type); + } + + @Override + public boolean isInsideStructure(World world, String type, BlockPos position) { + return "Stronghold".equals(type) && this.strongholdGen != null ? this.strongholdGen.isInsideStructure(position) : false; + } + + @Override + public BlockPos getNearestStructurePos(World world, String type, BlockPos position, boolean flag) { + return "Stronghold".equals(type) && this.strongholdGen != null ? this.strongholdGen.getNearestStructurePos(world, position, flag) : null; + } + + @Override + public void populate(int i, int j) {} + + @Override + public void recreateStructures(Chunk chunk, int i, int j) { + strongholdGen.generate(this.world, i, j, null); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/InternalChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/InternalChunkGenerator.java new file mode 100644 index 00000000..f329eedf --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/InternalChunkGenerator.java @@ -0,0 +1,8 @@ +package org.bukkit.craftbukkit.generator; + +import net.minecraft.world.gen.IChunkGenerator; +import org.bukkit.generator.ChunkGenerator; + +// Do not implement functions to this class, add to NormalChunkGenerator +public abstract class InternalChunkGenerator extends ChunkGenerator implements IChunkGenerator { +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/NetherChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/NetherChunkGenerator.java new file mode 100644 index 00000000..20cf3799 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/NetherChunkGenerator.java @@ -0,0 +1,12 @@ +package org.bukkit.craftbukkit.generator; + +import net.minecraft.world.World; + +/** + * This class is useless. Just fyi. + */ +public class NetherChunkGenerator extends NormalChunkGenerator { + public NetherChunkGenerator(World world, long seed) { + super(world, seed); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/NormalChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/NormalChunkGenerator.java new file mode 100644 index 00000000..cd69abf0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/NormalChunkGenerator.java @@ -0,0 +1,73 @@ +package org.bukkit.craftbukkit.generator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import net.minecraft.entity.EnumCreatureType; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.gen.IChunkGenerator; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.generator.BlockPopulator; + +public class NormalChunkGenerator extends InternalChunkGenerator { + private final IChunkGenerator generator; + + public NormalChunkGenerator(World world, long seed) { + generator = world.provider.createChunkGenerator(); + } + + @Override + public byte[] generate(org.bukkit.World world, Random random, int x, int z) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean canSpawn(org.bukkit.World world, int x, int z) { + return ((CraftWorld) world).getHandle().provider.canCoordinateBeSpawn(x, z); + } + + @Override + public List getDefaultPopulators(org.bukkit.World world) { + return new ArrayList<>(); + } + + @Override + public Chunk generateChunk(int i, int i1) { + return generator.generateChunk(i, i1); + } + + @Override + public void populate(int i, int i1) { + generator.populate(i, i1); + } + + @Override + public boolean generateStructures(Chunk chunk, int i, int i1) { + return generator.generateStructures(chunk, i, i1); + } + + @Override + public List getPossibleCreatures(EnumCreatureType enumCreatureType, BlockPos blockPosition) { + return generator.getPossibleCreatures(enumCreatureType, blockPosition); + } + + @Override + public BlockPos getNearestStructurePos(World world, String s, BlockPos blockPosition, boolean flag) { + return generator.getNearestStructurePos(world, s, blockPosition, flag); + } + + @Override + public void recreateStructures(Chunk chunk, int i, int i1) { + generator.recreateStructures(chunk, i, i1); + } + + @Override + public boolean isInsideStructure(World world, String string, BlockPos bp) { + return generator.isInsideStructure(world, string, bp); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/generator/SkyLandsChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/SkyLandsChunkGenerator.java new file mode 100644 index 00000000..fca430f0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/generator/SkyLandsChunkGenerator.java @@ -0,0 +1,12 @@ +package org.bukkit.craftbukkit.generator; + +import net.minecraft.world.World; + +/** + * This class is useless. Just fyi. + */ +public class SkyLandsChunkGenerator extends NormalChunkGenerator { + public SkyLandsChunkGenerator(World world, long seed) { + super(world, seed); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/CommandAliasHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/CommandAliasHelpTopic.java new file mode 100644 index 00000000..928a2dfd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/CommandAliasHelpTopic.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.help; + +import org.apache.commons.lang3.Validate; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; + +public class CommandAliasHelpTopic extends HelpTopic { + + private final String aliasFor; + private final HelpMap helpMap; + + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { + this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; + this.helpMap = helpMap; + this.name = alias.startsWith("/") ? alias : "/" + alias; + Validate.isTrue(!this.name.equals(this.aliasFor), "Command " + this.name + " cannot be alias for itself"); + this.shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor; + } + + @Override + public String getFullText(CommandSender forWho) { + StringBuilder sb = new StringBuilder(shortText); + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + if (aliasForTopic != null) { + sb.append("\n"); + sb.append(aliasForTopic.getFullText(forWho)); + } + return sb.toString(); + } + + @Override + public boolean canSee(CommandSender commandSender) { + if (amendedPermission == null) { + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + if (aliasForTopic != null) { + return aliasForTopic.canSee(commandSender); + } else { + return false; + } + } else { + return commandSender.hasPermission(amendedPermission); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java new file mode 100644 index 00000000..6dee2296 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.help.HelpTopic; + +/** + * This is a help topic implementation for general topics registered in the help.yml file. + */ +public class CustomHelpTopic extends HelpTopic { + private final String permissionNode; + + public CustomHelpTopic(String name, String shortText, String fullText, String permissionNode) { + this.permissionNode = permissionNode; + this.name = name; + this.shortText = shortText; + this.fullText = shortText + "\n" + fullText; + } + + public boolean canSee(CommandSender sender) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + + if (!permissionNode.equals("")) { + return sender.hasPermission(permissionNode); + } else { + return true; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/CustomIndexHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/CustomIndexHelpTopic.java new file mode 100644 index 00000000..2089a5f5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/CustomIndexHelpTopic.java @@ -0,0 +1,40 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.IndexHelpTopic; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + */ +public class CustomIndexHelpTopic extends IndexHelpTopic { + private List futureTopics; + private final HelpMap helpMap; + + public CustomIndexHelpTopic(HelpMap helpMap, String name, String shortText, String permission, List futureTopics, String preamble) { + super(name, shortText, permission, new HashSet(), preamble); + this.helpMap = helpMap; + this.futureTopics = futureTopics; + } + + @Override + public String getFullText(CommandSender sender) { + if (futureTopics != null) { + List topics = new LinkedList(); + for (String futureTopic : futureTopics) { + HelpTopic topic = helpMap.getHelpTopic(futureTopic); + if (topic != null) { + topics.add(topic); + } + } + setTopicsCollection(topics); + futureTopics = null; + } + + return super.getFullText(sender); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java new file mode 100644 index 00000000..4f0e00ec --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java @@ -0,0 +1,50 @@ +package org.bukkit.craftbukkit.help; + +/** + * A HelpTopicAmendment represents the contents of a topic amendment from the help.yml + */ +public class HelpTopicAmendment { + private final String topicName; + private final String shortText; + private final String fullText; + private final String permission; + + public HelpTopicAmendment(String topicName, String shortText, String fullText, String permission) { + this.fullText = fullText; + this.shortText = shortText; + this.topicName = topicName; + this.permission = permission; + } + + /** + * Gets the amended full text + * @return the full text + */ + public String getFullText() { + return fullText; + } + + /** + * Gets the amended short text + * @return the short text + */ + public String getShortText() { + return shortText; + } + + /** + * Gets the name of the topic being amended + * @return the topic name + */ + public String getTopicName() { + return topicName; + } + + /** + * Gets the amended permission + * @return the permission + */ + public String getPermission() { + return permission; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java b/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java new file mode 100644 index 00000000..60a6221b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java @@ -0,0 +1,119 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.Server; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.help.HelpTopic; + +import com.google.common.base.Charsets; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; + +/** + * HelpYamlReader is responsible for processing the contents of the help.yml file. + */ +public class HelpYamlReader { + + private YamlConfiguration helpYaml; + private final char ALT_COLOR_CODE = '&'; + private final Server server; + + public HelpYamlReader(Server server) { + this.server = server; + + File helpYamlFile = new File("help.yml"); + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("configurations/help.yml"), Charsets.UTF_8)); + + try { + helpYaml = YamlConfiguration.loadConfiguration(helpYamlFile); + helpYaml.options().copyDefaults(true); + helpYaml.setDefaults(defaultConfig); + + try { + if (!helpYamlFile.exists()) { + helpYaml.save(helpYamlFile); + } + } catch (IOException ex) { + server.getLogger().log(Level.SEVERE, "Could not save " + helpYamlFile, ex); + } + } catch (Exception ex) { + server.getLogger().severe("Failed to load help.yml. Verify the yaml indentation is correct. Reverting to default help.yml."); + helpYaml = defaultConfig; + } + } + + /** + * Extracts a list of all general help topics from help.yml + * + * @return A list of general topics. + */ + public List getGeneralTopics() { + List topics = new LinkedList(); + ConfigurationSection generalTopics = helpYaml.getConfigurationSection("general-topics"); + if (generalTopics != null) { + for (String topicName : generalTopics.getKeys(false)) { + ConfigurationSection section = generalTopics.getConfigurationSection(topicName); + String shortText = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("shortText", "")); + String fullText = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("fullText", "")); + String permission = section.getString("permission", ""); + topics.add(new CustomHelpTopic(topicName, shortText, fullText, permission)); + } + } + return topics; + } + + /** + * Extracts a list of all index topics from help.yml + * + * @return A list of index topics. + */ + public List getIndexTopics() { + List topics = new LinkedList(); + ConfigurationSection indexTopics = helpYaml.getConfigurationSection("index-topics"); + if (indexTopics != null) { + for (String topicName : indexTopics.getKeys(false)) { + ConfigurationSection section = indexTopics.getConfigurationSection(topicName); + String shortText = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("shortText", "")); + String preamble = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("preamble", "")); + String permission = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("permission", "")); + List commands = section.getStringList("commands"); + topics.add(new CustomIndexHelpTopic(server.getHelpMap(), topicName, shortText, permission, commands, preamble)); + } + } + return topics; + } + + /** + * Extracts a list of topic amendments from help.yml + * + * @return A list of amendments. + */ + public List getTopicAmendments() { + List amendments = new LinkedList(); + ConfigurationSection commandTopics = helpYaml.getConfigurationSection("amended-topics"); + if (commandTopics != null) { + for (String topicName : commandTopics.getKeys(false)) { + ConfigurationSection section = commandTopics.getConfigurationSection(topicName); + String description = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("shortText", "")); + String usage = ChatColor.translateAlternateColorCodes(ALT_COLOR_CODE, section.getString("fullText", "")); + String permission = section.getString("permission", ""); + amendments.add(new HelpTopicAmendment(topicName, description, usage, permission)); + } + } + return amendments; + } + + public List getIgnoredPlugins() { + return helpYaml.getStringList("ignore-plugins"); + } + + public boolean commandTopicsInMasterIndex() { + return helpYaml.getBoolean("command-topics-in-master-index", true); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java new file mode 100644 index 00000000..6f4b22b9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java @@ -0,0 +1,54 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.MultipleCommandAlias; +import org.bukkit.help.HelpTopic; + +/** + * This is a help topic implementation for {@link MultipleCommandAlias} commands. + */ +public class MultipleCommandAliasHelpTopic extends HelpTopic { + + private final MultipleCommandAlias alias; + + public MultipleCommandAliasHelpTopic(MultipleCommandAlias alias) { + this.alias = alias; + + name = "/" + alias.getLabel(); + + // Build short text + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < alias.getCommands().length; i++) { + if (i != 0) { + sb.append(ChatColor.GOLD + " > " + ChatColor.WHITE); + } + sb.append("/"); + sb.append(alias.getCommands()[i].getLabel()); + } + shortText = sb.toString(); + + // Build full text + fullText = ChatColor.GOLD + "Alias for: " + ChatColor.WHITE + getShortText(); + } + + public boolean canSee(CommandSender sender) { + if (amendedPermission == null) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + + for (Command command : alias.getCommands()) { + if (!command.testPermissionSilent(sender)) { + return false; + } + } + + return true; + } else { + return sender.hasPermission(amendedPermission); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java new file mode 100644 index 00000000..36ddc976 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java @@ -0,0 +1,15 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.MultipleCommandAlias; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicFactory; + +/** + * This class creates {@link MultipleCommandAliasHelpTopic} help topics from {@link MultipleCommandAlias} commands. + */ +public class MultipleCommandAliasHelpTopicFactory implements HelpTopicFactory { + + public HelpTopic createTopic(MultipleCommandAlias multipleCommandAlias) { + return new MultipleCommandAliasHelpTopic(multipleCommandAlias); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java new file mode 100644 index 00000000..c7daccd5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java @@ -0,0 +1,218 @@ +package org.bukkit.craftbukkit.help; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2; + +import org.bukkit.command.*; +import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.command.VanillaCommandWrapper; +import org.bukkit.help.*; + +import java.util.*; + +/** + * Standard implementation of {@link HelpMap} for CraftBukkit servers. + */ +public class SimpleHelpMap implements HelpMap { + + private HelpTopic defaultTopic; + private final Map helpTopics; + private final Map> topicFactoryMap; + private final CraftServer server; + private HelpYamlReader yaml; + + @SuppressWarnings("unchecked") + public SimpleHelpMap(CraftServer server) { + this.helpTopics = new TreeMap(HelpTopicComparator.topicNameComparatorInstance()); // Using a TreeMap for its explicit sorting on key + this.topicFactoryMap = new HashMap>(); + this.server = server; + this.yaml = new HelpYamlReader(server); + + Predicate indexFilter = Predicates.not(Predicates.instanceOf(CommandAliasHelpTopic.class)); + if (!yaml.commandTopicsInMasterIndex()) { + indexFilter = Predicates.and(indexFilter, Predicates.not(new IsCommandTopicPredicate())); + } + + this.defaultTopic = new IndexHelpTopic("Index", null, null, Collections2.filter(helpTopics.values(), indexFilter), "Use /help [n] to get page n of help."); + + registerHelpTopicFactory(MultipleCommandAlias.class, new MultipleCommandAliasHelpTopicFactory()); + } + + public synchronized HelpTopic getHelpTopic(String topicName) { + if (topicName.equals("")) { + return defaultTopic; + } + + if (helpTopics.containsKey(topicName)) { + return helpTopics.get(topicName); + } + + return null; + } + + public Collection getHelpTopics() { + return helpTopics.values(); + } + + public synchronized void addTopic(HelpTopic topic) { + // Existing topics take priority + if (!helpTopics.containsKey(topic.getName())) { + helpTopics.put(topic.getName(), topic); + } + } + + public synchronized void clear() { + helpTopics.clear(); + } + + public List getIgnoredPlugins() { + return yaml.getIgnoredPlugins(); + } + + /** + * Reads the general topics from help.yml and adds them to the help index. + */ + public synchronized void initializeGeneralTopics() { + yaml = new HelpYamlReader(server); + + // Initialize general help topics from the help.yml file + for (HelpTopic topic : yaml.getGeneralTopics()) { + addTopic(topic); + } + + // Initialize index help topics from the help.yml file + for (HelpTopic topic : yaml.getIndexTopics()) { + if (topic.getName().equals("Default")) { + defaultTopic = topic; + } else { + addTopic(topic); + } + } + } + + /** + * Processes all the commands registered in the server and creates help topics for them. + */ + public synchronized void initializeCommands() { + // ** Load topics from highest to lowest priority order ** + Set ignoredPlugins = new HashSet(yaml.getIgnoredPlugins()); + + // Don't load any automatic help topics if All is ignored + if (ignoredPlugins.contains("All")) { + return; + } + + // Initialize help topics from the server's command map + outer: for (Command command : server.getCommandMap().getCommands()) { + if (commandInIgnoredPlugin(command, ignoredPlugins)) { + continue; + } + + // Register a topic + for (Class c : topicFactoryMap.keySet()) { + if (c.isAssignableFrom(command.getClass())) { + HelpTopic t = topicFactoryMap.get(c).createTopic(command); + if (t != null) addTopic(t); + continue outer; + } + if (command instanceof PluginCommand && c.isAssignableFrom(((PluginCommand)command).getExecutor().getClass())) { + HelpTopic t = topicFactoryMap.get(c).createTopic(command); + if (t != null) addTopic(t); + continue outer; + } + } + addTopic(new GenericCommandHelpTopic(command)); + } + + // Initialize command alias help topics + for (Command command : server.getCommandMap().getCommands()) { + if (commandInIgnoredPlugin(command, ignoredPlugins)) { + continue; + } + for (String alias : command.getAliases()) { + // Only register if this command owns the alias + if (server.getCommandMap().getCommand(alias) == command) { + addTopic(new CommandAliasHelpTopic("/" + alias, "/" + command.getLabel(), this)); + } + } + } + + // Add alias sub-index + Collection filteredTopics = Collections2.filter(helpTopics.values(), Predicates.instanceOf(CommandAliasHelpTopic.class)); + if (!filteredTopics.isEmpty()) { + addTopic(new IndexHelpTopic("Aliases", "Lists command aliases", null, filteredTopics)); + } + + // Initialize plugin-level sub-topics + Map> pluginIndexes = new HashMap>(); + fillPluginIndexes(pluginIndexes, server.getCommandMap().getCommands()); + + for (Map.Entry> entry : pluginIndexes.entrySet()) { + addTopic(new IndexHelpTopic(entry.getKey(), "All commands for " + entry.getKey(), null, entry.getValue(), "Below is a list of all " + entry.getKey() + " commands:")); + } + + // Amend help topics from the help.yml file + for (HelpTopicAmendment amendment : yaml.getTopicAmendments()) { + if (helpTopics.containsKey(amendment.getTopicName())) { + helpTopics.get(amendment.getTopicName()).amendTopic(amendment.getShortText(), amendment.getFullText()); + if (amendment.getPermission() != null) { + helpTopics.get(amendment.getTopicName()).amendCanSee(amendment.getPermission()); + } + } + } + } + + private void fillPluginIndexes(Map> pluginIndexes, Collection commands) { + for (Command command : commands) { + String pluginName = getCommandPluginName(command); + if (pluginName != null) { + HelpTopic topic = getHelpTopic("/" + command.getLabel()); + if (topic != null) { + if (!pluginIndexes.containsKey(pluginName)) { + pluginIndexes.put(pluginName, new TreeSet(HelpTopicComparator.helpTopicComparatorInstance())); //keep things in topic order + } + pluginIndexes.get(pluginName).add(topic); + } + } + } + } + + private String getCommandPluginName(Command command) { + if (command instanceof VanillaCommandWrapper) { + return "Minecraft"; + } + if (command instanceof BukkitCommand) { + return "Bukkit"; + } + if (command instanceof PluginIdentifiableCommand) { + return ((PluginIdentifiableCommand)command).getPlugin().getName(); + } + return null; + } + + private boolean commandInIgnoredPlugin(Command command, Set ignoredPlugins) { + if ((command instanceof BukkitCommand) && ignoredPlugins.contains("Bukkit")) { + return true; + } + if (command instanceof PluginIdentifiableCommand && ignoredPlugins.contains(((PluginIdentifiableCommand)command).getPlugin().getName())) { + return true; + } + return false; + } + + public void registerHelpTopicFactory(Class commandClass, HelpTopicFactory factory) { + if (!Command.class.isAssignableFrom(commandClass) && !CommandExecutor.class.isAssignableFrom(commandClass)) { + throw new IllegalArgumentException("commandClass must implement either Command or CommandExecutor!"); + } + topicFactoryMap.put(commandClass, factory); + } + + private class IsCommandTopicPredicate implements Predicate { + + public boolean apply(HelpTopic topic) { + return topic.getName().charAt(0) == '/'; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java new file mode 100644 index 00000000..f972e96a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -0,0 +1,213 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.ContainerBeacon; +import net.minecraft.inventory.ContainerBrewingStand; +import net.minecraft.inventory.ContainerChest; +import net.minecraft.inventory.ContainerDispenser; +import net.minecraft.inventory.ContainerEnchantment; +import net.minecraft.inventory.ContainerFurnace; +import net.minecraft.inventory.ContainerHopper; +import net.minecraft.inventory.ContainerRepair; +import net.minecraft.inventory.ContainerShulkerBox; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.server.SPacketOpenWindow; +import net.minecraft.util.text.TextComponentString; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; + +public class CraftContainer extends Container { + + private final InventoryView view; + private InventoryType cachedType; + private String cachedTitle; + private Container delegate; + private final int cachedSize; + + public CraftContainer(InventoryView view, EntityPlayer player, int id) { + this.view = view; + this.windowId = id; + // TODO: Do we need to check that it really is a CraftInventory? + IInventory top = ((CraftInventory) view.getTopInventory()).getInventory(); + InventoryPlayer bottom = (InventoryPlayer) ((CraftInventory) view.getBottomInventory()).getInventory(); + cachedType = view.getType(); + cachedTitle = view.getTitle(); + cachedSize = getSize(); + setupSlots(top, bottom, player); + } + + public CraftContainer(final Inventory inventory, final EntityPlayer player, int id) { + this(new InventoryView() { + @Override + public Inventory getTopInventory() { + return inventory; + } + + @Override + public Inventory getBottomInventory() { + return getPlayer().getInventory(); + } + + @Override + public HumanEntity getPlayer() { + return player.getBukkitEntity(); + } + + @Override + public InventoryType getType() { + return inventory.getType(); + } + }, player, id); + } + + @Override + public InventoryView getBukkitView() { + return view; + } + + private int getSize() { + return view.getTopInventory().getSize(); + } + + @Override + public boolean getCanCraft(EntityPlayer entityhuman) { + if (cachedType == view.getType() && cachedSize == getSize() && cachedTitle.equals(view.getTitle())) { + return true; + } + // If the window type has changed for some reason, update the player + // This method will be called every tick or something, so it's + // as good a place as any to put something like this. + boolean typeChanged = (cachedType != view.getType()); + cachedType = view.getType(); + cachedTitle = view.getTitle(); + if (view.getPlayer() instanceof CraftPlayer) { + CraftPlayer player = (CraftPlayer) view.getPlayer(); + String type = getNotchInventoryType(cachedType); + IInventory top = ((CraftInventory) view.getTopInventory()).getInventory(); + InventoryPlayer bottom = (InventoryPlayer) ((CraftInventory) view.getBottomInventory()).getInventory(); + this.inventoryItemStacks.clear(); + this.inventorySlots.clear(); + if (typeChanged) { + setupSlots(top, bottom, player.getHandle()); + } + int size = getSize(); + player.getHandle().connection.sendPacket(new SPacketOpenWindow(this.windowId, type, new TextComponentString(cachedTitle), size)); + player.updateInventory(); + } + return true; + } + + public static String getNotchInventoryType(InventoryType type) { + switch (type) { + case WORKBENCH: + return "minecraft:crafting_table"; + case FURNACE: + return "minecraft:furnace"; + case DISPENSER: + return "minecraft:dispenser"; + case ENCHANTING: + return "minecraft:enchanting_table"; + case BREWING: + return "minecraft:brewing_stand"; + case BEACON: + return "minecraft:beacon"; + case ANVIL: + return "minecraft:anvil"; + case HOPPER: + return "minecraft:hopper"; + case DROPPER: + return "minecraft:dropper"; + case SHULKER_BOX: + return "minecraft:shulker_box"; + default: + return "minecraft:chest"; + } + } + + private void setupSlots(IInventory top, InventoryPlayer bottom, EntityPlayer entityhuman) { + switch (cachedType) { + case CREATIVE: + break; // TODO: This should be an error? + case PLAYER: + case CHEST: + delegate = new ContainerChest(bottom, top, entityhuman); + break; + case DISPENSER: + case DROPPER: + delegate = new ContainerDispenser(bottom, top); + break; + case FURNACE: + delegate = new ContainerFurnace(bottom, top); + break; + case CRAFTING: // TODO: This should be an error? + case WORKBENCH: + setupWorkbench(top, bottom); // SPIGOT-3812 - manually set up slots so we can use the delegated inventory and not the automatically created one + break; + case ENCHANTING: + delegate = new ContainerEnchantment(bottom, entityhuman.world, entityhuman.getPosition()); + break; + case BREWING: + delegate = new ContainerBrewingStand(bottom, top); + break; + case HOPPER: + delegate = new ContainerHopper(bottom, top, entityhuman); + break; + case ANVIL: + delegate = new ContainerRepair(bottom, entityhuman.world, entityhuman.getPosition(), entityhuman); + break; + case BEACON: + delegate = new ContainerBeacon(bottom, top); + break; + case SHULKER_BOX: + delegate = new ContainerShulkerBox(bottom, top, entityhuman); + break; + } + + if (delegate != null) { + this.inventoryItemStacks = delegate.inventoryItemStacks; + this.inventorySlots = delegate.inventorySlots; + } + } + + private void setupWorkbench(IInventory top, IInventory bottom) { + // This code copied from ContainerWorkbench + this.addSlotToContainer(new Slot(top, 0, 124, 35)); + + int row; + int col; + + for (row = 0; row < 3; ++row) { + for (col = 0; col < 3; ++col) { + this.addSlotToContainer(new Slot(top, 1 + col + row * 3, 30 + col * 18, 17 + row * 18)); + } + } + + for (row = 0; row < 3; ++row) { + for (col = 0; col < 9; ++col) { + this.addSlotToContainer(new Slot(bottom, col + row * 9 + 9, 8 + col * 18, 84 + row * 18)); + } + } + + for (col = 0; col < 9; ++col) { + this.addSlotToContainer(new Slot(bottom, col, 8 + col * 18, 142)); + } + // End copy from ContainerWorkbench + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer entityhuman, int i) { + return (delegate != null) ? delegate.transferStackInSlot(entityhuman, i) : super.transferStackInSlot(entityhuman, i); + } + + @Override + public boolean canInteractWith(EntityPlayer entity) { + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomModRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomModRecipe.java new file mode 100644 index 00000000..ffd689cc --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomModRecipe.java @@ -0,0 +1,25 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.item.crafting.IRecipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; + +/** + * Bukkit API wrapper for non-vanilla IRecipe classes + */ +public class CraftCustomModRecipe implements Recipe { + private IRecipe iRecipe; + + public CraftCustomModRecipe(IRecipe iRecipe) { + this.iRecipe = iRecipe; + } + + @Override + public ItemStack getResult() { + return CraftItemStack.asCraftMirror(iRecipe.getRecipeOutput()); + } + + public IRecipe getHandle() { + return iRecipe; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java new file mode 100644 index 00000000..cc87d8a8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java @@ -0,0 +1,192 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.entity.EntityLiving; +import net.minecraft.inventory.EntityEquipmentSlot; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; + +public class CraftEntityEquipment implements EntityEquipment { + + private final CraftLivingEntity entity; + + public CraftEntityEquipment(CraftLivingEntity entity) { + this.entity = entity; + } + + @Override + public ItemStack getItemInMainHand() { + return getEquipment(EntityEquipmentSlot.MAINHAND); + } + + @Override + public void setItemInMainHand(ItemStack item) { + setEquipment(EntityEquipmentSlot.MAINHAND, item); + } + + @Override + public ItemStack getItemInOffHand() { + return getEquipment(EntityEquipmentSlot.OFFHAND); + } + + @Override + public void setItemInOffHand(ItemStack item) { + setEquipment(EntityEquipmentSlot.OFFHAND, item); + } + + @Override + public ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(ItemStack stack) { + setItemInMainHand(stack); + } + + public ItemStack getHelmet() { + return getEquipment(EntityEquipmentSlot.HEAD); + } + + public void setHelmet(ItemStack helmet) { + setEquipment(EntityEquipmentSlot.HEAD, helmet); + } + + public ItemStack getChestplate() { + return getEquipment(EntityEquipmentSlot.CHEST); + } + + public void setChestplate(ItemStack chestplate) { + setEquipment(EntityEquipmentSlot.CHEST, chestplate); + } + + public ItemStack getLeggings() { + return getEquipment(EntityEquipmentSlot.LEGS); + } + + public void setLeggings(ItemStack leggings) { + setEquipment(EntityEquipmentSlot.LEGS, leggings); + } + + public ItemStack getBoots() { + return getEquipment(EntityEquipmentSlot.FEET); + } + + public void setBoots(ItemStack boots) { + setEquipment(EntityEquipmentSlot.FEET, boots); + } + + public ItemStack[] getArmorContents() { + ItemStack[] armor = new ItemStack[]{ + getEquipment(EntityEquipmentSlot.FEET), + getEquipment(EntityEquipmentSlot.LEGS), + getEquipment(EntityEquipmentSlot.CHEST), + getEquipment(EntityEquipmentSlot.HEAD), + }; + return armor; + } + + public void setArmorContents(ItemStack[] items) { + setEquipment(EntityEquipmentSlot.FEET, items.length >= 1 ? items[0] : null); + setEquipment(EntityEquipmentSlot.LEGS, items.length >= 2 ? items[1] : null); + setEquipment(EntityEquipmentSlot.CHEST, items.length >= 3 ? items[2] : null); + setEquipment(EntityEquipmentSlot.HEAD, items.length >= 4 ? items[3] : null); + } + + private ItemStack getEquipment(EntityEquipmentSlot slot) { + return CraftItemStack.asBukkitCopy(entity.getHandle().getItemStackFromSlot(slot)); + } + + private void setEquipment(EntityEquipmentSlot slot, ItemStack stack) { + entity.getHandle().setItemStackToSlot(slot, CraftItemStack.asNMSCopy(stack)); + } + + public void clear() { + for (EntityEquipmentSlot slot : EntityEquipmentSlot.values()) { + setEquipment(slot, null); + } + } + + public Entity getHolder() { + return entity; + } + + @Override + public float getItemInHandDropChance() { + return getItemInMainHandDropChance(); + } + + @Override + public void setItemInHandDropChance(float chance) { + setItemInMainHandDropChance(chance); + } + + @Override + public float getItemInMainHandDropChance() { + return getDropChance(EntityEquipmentSlot.MAINHAND); + } + + @Override + public void setItemInMainHandDropChance(float chance) { + setDropChance(EntityEquipmentSlot.MAINHAND, chance); + } + + @Override + public float getItemInOffHandDropChance() { + return getDropChance(EntityEquipmentSlot.OFFHAND); + } + + @Override + public void setItemInOffHandDropChance(float chance) { + setDropChance(EntityEquipmentSlot.OFFHAND, chance); + } + + public float getHelmetDropChance() { + return getDropChance(EntityEquipmentSlot.HEAD); + } + + public void setHelmetDropChance(float chance) { + setDropChance(EntityEquipmentSlot.HEAD, chance); + } + + public float getChestplateDropChance() { + return getDropChance(EntityEquipmentSlot.CHEST); + } + + public void setChestplateDropChance(float chance) { + setDropChance(EntityEquipmentSlot.CHEST, chance); + } + + public float getLeggingsDropChance() { + return getDropChance(EntityEquipmentSlot.LEGS); + } + + public void setLeggingsDropChance(float chance) { + setDropChance(EntityEquipmentSlot.LEGS, chance); + } + + public float getBootsDropChance() { + return getDropChance(EntityEquipmentSlot.FEET); + } + + public void setBootsDropChance(float chance) { + setDropChance(EntityEquipmentSlot.FEET, chance); + } + + private void setDropChance(EntityEquipmentSlot slot, float chance) { + if (slot == EntityEquipmentSlot.MAINHAND || slot == EntityEquipmentSlot.OFFHAND) { + ((EntityLiving) entity.getHandle()).inventoryHandsDropChances[slot.getIndex()] = chance - 0.1F; + } else { + ((EntityLiving) entity.getHandle()).inventoryArmorDropChances[slot.getIndex()] = chance - 0.1F; + } + } + + private float getDropChance(EntityEquipmentSlot slot) { + if (slot == EntityEquipmentSlot.MAINHAND || slot == EntityEquipmentSlot.OFFHAND) { + return ((EntityLiving) entity.getHandle()).inventoryHandsDropChances[slot.getIndex()] + 0.1F; + } else { + return ((EntityLiving) entity.getHandle()).inventoryArmorDropChances[slot.getIndex()] + 0.1F; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java new file mode 100644 index 00000000..9ce5fc2e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java @@ -0,0 +1,25 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.item.crafting.FurnaceRecipes; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.ItemStack; + +public class CraftFurnaceRecipe extends FurnaceRecipe implements CraftRecipe { + public CraftFurnaceRecipe(ItemStack result, ItemStack source) { + super(result, source.getType(), source.getDurability()); + } + + public static CraftFurnaceRecipe fromBukkitRecipe(FurnaceRecipe recipe) { + if (recipe instanceof CraftFurnaceRecipe) { + return (CraftFurnaceRecipe) recipe; + } + return new CraftFurnaceRecipe(recipe.getResult(), recipe.getInput()); + } + + @Override + public void addToCraftingManager() { + ItemStack result = this.getResult(); + ItemStack input = this.getInput(); + FurnaceRecipes.instance().registerRecipe(CraftItemStack.asNMSCopy(input), CraftItemStack.asNMSCopy(result), getExperience()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java new file mode 100644 index 00000000..dfec100f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java @@ -0,0 +1,506 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.IInventory; + +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.inventory.InventoryEnderChest; +import net.minecraft.inventory.InventoryMerchant; +import net.minecraft.tileentity.IHopper; +import net.minecraft.tileentity.TileEntityBeacon; +import net.minecraft.tileentity.TileEntityBrewingStand; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.tileentity.TileEntityDropper; +import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraft.tileentity.TileEntityShulkerBox; +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; + +public class CraftInventory implements Inventory { + protected final IInventory inventory; + + public CraftInventory(IInventory inventory) { + this.inventory = inventory; + } + + public IInventory getInventory() { + return inventory; + } + + public int getSize() { + return getInventory().getSizeInventory(); + } + + public String getName() { + return getInventory().getName(); + } + + public ItemStack getItem(int index) { + net.minecraft.item.ItemStack item = getInventory().getStackInSlot(index); + return item.isEmpty() ? null : CraftItemStack.asCraftMirror(item); + } + + protected ItemStack[] asCraftMirror(List mcItems) { + int size = mcItems.size(); + ItemStack[] items = new ItemStack[size]; + + for (int i = 0; i < size; i++) { + net.minecraft.item.ItemStack mcItem = mcItems.get(i); + items[i] = (mcItem.isEmpty()) ? null : CraftItemStack.asCraftMirror(mcItem); + } + + return items; + } + + @Override + public ItemStack[] getStorageContents() { + return getContents(); + } + + @Override + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + setContents(items); + } + + public ItemStack[] getContents() { + List mcItems = getInventory().getContents(); + + return asCraftMirror(mcItems); + } + + public void setContents(ItemStack[] items) { + if (getSize() < items.length) { + throw new IllegalArgumentException("Invalid inventory size; expected " + getSize() + " or less"); + } + + for (int i = 0; i < getSize(); i++) { + if (i >= items.length) { + setItem(i, null); + } else { + setItem(i, items[i]); + } + } + } + + public void setItem(int index, ItemStack item) { + getInventory().setInventorySlotContents(index, CraftItemStack.asNMSCopy(item)); + } + + public boolean contains(int materialId) { + for (ItemStack item : getStorageContents()) { + if (item != null && item.getTypeId() == materialId) { + return true; + } + } + return false; + } + + public boolean contains(Material material) { + Validate.notNull(material, "Material cannot be null"); + return contains(material.getId()); + } + + public boolean contains(ItemStack item) { + if (item == null) { + return false; + } + for (ItemStack i : getStorageContents()) { + if (item.equals(i)) { + return true; + } + } + return false; + } + + public boolean contains(int materialId, int amount) { + if (amount <= 0) { + return true; + } + for (ItemStack item : getStorageContents()) { + if (item != null && item.getTypeId() == materialId) { + if ((amount -= item.getAmount()) <= 0) { + return true; + } + } + } + return false; + } + + public boolean contains(Material material, int amount) { + Validate.notNull(material, "Material cannot be null"); + return contains(material.getId(), amount); + } + + public boolean contains(ItemStack item, int amount) { + if (item == null) { + return false; + } + if (amount <= 0) { + return true; + } + for (ItemStack i : getStorageContents()) { + if (item.equals(i) && --amount <= 0) { + return true; + } + } + return false; + } + + public boolean containsAtLeast(ItemStack item, int amount) { + if (item == null) { + return false; + } + if (amount <= 0) { + return true; + } + for (ItemStack i : getStorageContents()) { + if (item.isSimilar(i) && (amount -= i.getAmount()) <= 0) { + return true; + } + } + return false; + } + + public HashMap all(int materialId) { + HashMap slots = new HashMap(); + + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + ItemStack item = inventory[i]; + if (item != null && item.getTypeId() == materialId) { + slots.put(i, item); + } + } + return slots; + } + + public HashMap all(Material material) { + Validate.notNull(material, "Material cannot be null"); + return all(material.getId()); + } + + public HashMap all(ItemStack item) { + HashMap slots = new HashMap(); + if (item != null) { + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + if (item.equals(inventory[i])) { + slots.put(i, inventory[i]); + } + } + } + return slots; + } + + public int first(int materialId) { + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + ItemStack item = inventory[i]; + if (item != null && item.getTypeId() == materialId) { + return i; + } + } + return -1; + } + + public int first(Material material) { + Validate.notNull(material, "Material cannot be null"); + return first(material.getId()); + } + + public int first(ItemStack item) { + return first(item, true); + } + + private int first(ItemStack item, boolean withAmount) { + if (item == null) { + return -1; + } + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null) continue; + + if (withAmount ? item.equals(inventory[i]) : item.isSimilar(inventory[i])) { + return i; + } + } + return -1; + } + + public int firstEmpty() { + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null) { + return i; + } + } + return -1; + } + + public int firstPartial(int materialId) { + ItemStack[] inventory = getStorageContents(); + for (int i = 0; i < inventory.length; i++) { + ItemStack item = inventory[i]; + if (item != null && item.getTypeId() == materialId && item.getAmount() < item.getMaxStackSize()) { + return i; + } + } + return -1; + } + + public int firstPartial(Material material) { + Validate.notNull(material, "Material cannot be null"); + return firstPartial(material.getId()); + } + + private int firstPartial(ItemStack item) { + ItemStack[] inventory = getStorageContents(); + ItemStack filteredItem = CraftItemStack.asCraftCopy(item); + if (item == null) { + return -1; + } + for (int i = 0; i < inventory.length; i++) { + ItemStack cItem = inventory[i]; + if (cItem != null && cItem.getAmount() < cItem.getMaxStackSize() && cItem.isSimilar(filteredItem)) { + return i; + } + } + return -1; + } + + public HashMap addItem(ItemStack... items) { + Validate.noNullElements(items, "Item cannot be null"); + HashMap leftover = new HashMap(); + + /* TODO: some optimization + * - Create a 'firstPartial' with a 'fromIndex' + * - Record the lastPartial per Material + * - Cache firstEmpty result + */ + + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + while (true) { + // Do we already have a stack of it? + int firstPartial = firstPartial(item); + + // Drat! no partial stack + if (firstPartial == -1) { + // Find a free spot! + int firstFree = firstEmpty(); + + if (firstFree == -1) { + // No space at all! + leftover.put(i, item); + break; + } else { + // More than a single stack! + if (item.getAmount() > getMaxItemStack()) { + CraftItemStack stack = CraftItemStack.asCraftCopy(item); + stack.setAmount(getMaxItemStack()); + setItem(firstFree, stack); + item.setAmount(item.getAmount() - getMaxItemStack()); + } else { + // Just store it + setItem(firstFree, item); + break; + } + } + } else { + // So, apparently it might only partially fit, well lets do just that + ItemStack partialItem = getItem(firstPartial); + + int amount = item.getAmount(); + int partialAmount = partialItem.getAmount(); + int maxAmount = partialItem.getMaxStackSize(); + + // Check if it fully fits + if (amount + partialAmount <= maxAmount) { + partialItem.setAmount(amount + partialAmount); + // To make sure the packet is sent to the client + setItem(firstPartial, partialItem); + break; + } + + // It fits partially + partialItem.setAmount(maxAmount); + // To make sure the packet is sent to the client + setItem(firstPartial, partialItem); + item.setAmount(amount + partialAmount - maxAmount); + } + } + } + return leftover; + } + + public HashMap removeItem(ItemStack... items) { + Validate.notNull(items, "Items cannot be null"); + HashMap leftover = new HashMap(); + + // TODO: optimization + + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + int toDelete = item.getAmount(); + + while (true) { + int first = first(item, false); + + // Drat! we don't have this type in the inventory + if (first == -1) { + item.setAmount(toDelete); + leftover.put(i, item); + break; + } else { + ItemStack itemStack = getItem(first); + int amount = itemStack.getAmount(); + + if (amount <= toDelete) { + toDelete -= amount; + // clear the slot, all used up + clear(first); + } else { + // split the stack and store + itemStack.setAmount(amount - toDelete); + setItem(first, itemStack); + toDelete = 0; + } + } + + // Bail when done + if (toDelete <= 0) { + break; + } + } + } + return leftover; + } + + private int getMaxItemStack() { + return getInventory().getInventoryStackLimit(); + } + + public void remove(int materialId) { + ItemStack[] items = getStorageContents(); + for (int i = 0; i < items.length; i++) { + if (items[i] != null && items[i].getTypeId() == materialId) { + clear(i); + } + } + } + + public void remove(Material material) { + Validate.notNull(material, "Material cannot be null"); + remove(material.getId()); + } + + public void remove(ItemStack item) { + ItemStack[] items = getStorageContents(); + for (int i = 0; i < items.length; i++) { + if (items[i] != null && items[i].equals(item)) { + clear(i); + } + } + } + + public void clear(int index) { + setItem(index, null); + } + + public void clear() { + for (int i = 0; i < getSize(); i++) { + clear(i); + } + } + + public ListIterator iterator() { + return new InventoryIterator(this); + } + + public ListIterator iterator(int index) { + if (index < 0) { + index += getSize() + 1; // ie, with -1, previous() will return the last element + } + return new InventoryIterator(this, index); + } + + public List getViewers() { + return this.inventory.getViewers(); + } + + public String getTitle() { + return inventory.getName(); + } + + public InventoryType getType() { + // Thanks to Droppers extending Dispensers, order is important. + if (inventory instanceof InventoryCrafting) { + return inventory.getSizeInventory() >= 9 ? InventoryType.WORKBENCH : InventoryType.CRAFTING; + } else if (inventory instanceof InventoryPlayer) { + return InventoryType.PLAYER; + } else if (inventory instanceof TileEntityDropper) { + return InventoryType.DROPPER; + } else if (inventory instanceof TileEntityDispenser) { + return InventoryType.DISPENSER; + } else if (inventory instanceof TileEntityFurnace) { + return InventoryType.FURNACE; + } else if (this instanceof CraftInventoryEnchanting) { + return InventoryType.ENCHANTING; + } else if (inventory instanceof TileEntityBrewingStand) { + return InventoryType.BREWING; + } else if (inventory instanceof CraftInventoryCustom.MinecraftInventory) { + return ((CraftInventoryCustom.MinecraftInventory) inventory).getType(); + } else if (inventory instanceof InventoryEnderChest) { + return InventoryType.ENDER_CHEST; + } else if (inventory instanceof InventoryMerchant) { + return InventoryType.MERCHANT; + } else if (inventory instanceof TileEntityBeacon) { + return InventoryType.BEACON; + } else if (this instanceof CraftInventoryAnvil) { + return InventoryType.ANVIL; + } else if (inventory instanceof IHopper) { + return InventoryType.HOPPER; + } else if (inventory instanceof TileEntityShulkerBox) { + return InventoryType.SHULKER_BOX; + } else { + return InventoryType.CHEST; + } + } + + public InventoryHolder getHolder() { + return inventory.getOwner(); + } + + public int getMaxStackSize() { + return inventory.getInventoryStackLimit(); + } + + public void setMaxStackSize(int size) { + inventory.setMaxStackSize(size); + } + + @Override + public int hashCode() { + return inventory.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof CraftInventory && ((CraftInventory) obj).inventory.equals(this.inventory); + } + + @Override + public Location getLocation() { + return inventory.getLocation(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAbstractHorse.java new file mode 100644 index 00000000..977c30fa --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAbstractHorse.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.IInventory; +import org.bukkit.inventory.AbstractHorseInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryAbstractHorse extends CraftInventory implements AbstractHorseInventory { + + public CraftInventoryAbstractHorse(IInventory inventory) { + super(inventory); + } + + @Override + public ItemStack getSaddle() { + return getItem(0); + } + + @Override + public void setSaddle(ItemStack stack) { + setItem(0, stack); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java new file mode 100644 index 00000000..067459ff --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java @@ -0,0 +1,74 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.ContainerRepair; +import net.minecraft.inventory.IInventory; +import org.bukkit.Location; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryAnvil extends CraftInventory implements AnvilInventory { + + private final Location location; + private final IInventory resultInventory; + private final ContainerRepair container; + + public CraftInventoryAnvil(Location location, IInventory inventory, IInventory resultInventory, ContainerRepair container) { + super(inventory); + this.location = location; + this.resultInventory = resultInventory; + this.container = container; + } + + public IInventory getResultInventory() { + return resultInventory; + } + + public IInventory getIngredientsInventory() { + return inventory; + } + + @Override + public ItemStack getItem(int slot) { + if (slot < getIngredientsInventory().getSizeInventory()) { + net.minecraft.item.ItemStack item = getIngredientsInventory().getStackInSlot(slot); + return item.isEmpty() ? null : CraftItemStack.asCraftMirror(item); + } else { + net.minecraft.item.ItemStack item = getResultInventory().getStackInSlot(slot - getIngredientsInventory().getSizeInventory()); + return item.isEmpty() ? null : CraftItemStack.asCraftMirror(item); + } + } + + @Override + public void setItem(int index, ItemStack item) { + if (index < getIngredientsInventory().getSizeInventory()) { + getIngredientsInventory().setInventorySlotContents(index, CraftItemStack.asNMSCopy(item)); + } else { + getResultInventory().setInventorySlotContents((index - getIngredientsInventory().getSizeInventory()), CraftItemStack.asNMSCopy(item)); + } + } + + @Override + public int getSize() { + return getResultInventory().getSizeInventory() + getIngredientsInventory().getSizeInventory(); + } + + @Override + public Location getLocation() { + return location; + } + + @Override + public String getRenameText() { + return container.repairedItemName; + } + + @Override + public int getRepairCost() { + return container.maximumCost; + } + + @Override + public void setRepairCost(int i) { + container.maximumCost = i; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBeacon.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBeacon.java new file mode 100644 index 00000000..e471f752 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBeacon.java @@ -0,0 +1,19 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.tileentity.TileEntityBeacon; +import org.bukkit.inventory.BeaconInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryBeacon extends CraftInventory implements BeaconInventory { + public CraftInventoryBeacon(TileEntityBeacon beacon) { + super(beacon); + } + + public void setItem(ItemStack item) { + setItem(0, item); + } + + public ItemStack getItem() { + return getItem(0); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBrewer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBrewer.java new file mode 100644 index 00000000..ee365618 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryBrewer.java @@ -0,0 +1,35 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.IInventory; +import org.bukkit.block.BrewingStand; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryBrewer extends CraftInventory implements BrewerInventory { + public CraftInventoryBrewer(IInventory inventory) { + super(inventory); + } + + public ItemStack getIngredient() { + return getItem(3); + } + + public void setIngredient(ItemStack ingredient) { + setItem(3, ingredient); + } + + @Override + public BrewingStand getHolder() { + return (BrewingStand) inventory.getOwner(); + } + + @Override + public ItemStack getFuel() { + return getItem(4); + } + + @Override + public void setFuel(ItemStack fuel) { + setItem(4, fuel); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java new file mode 100644 index 00000000..edd1a649 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java @@ -0,0 +1,133 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Arrays; +import java.util.List; + +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.crafting.IRecipe; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; + +import javax.annotation.Nullable; + +public class CraftInventoryCrafting extends CraftInventory implements CraftingInventory { + private final IInventory resultInventory; + + public CraftInventoryCrafting(InventoryCrafting inventory, IInventory resultInventory) { + super(inventory); + this.resultInventory = resultInventory; + } + + public IInventory getResultInventory() { + return resultInventory; + } + + public IInventory getMatrixInventory() { + return inventory; + } + + @Override + public int getSize() { + return getResultInventory().getSizeInventory() + getMatrixInventory().getSizeInventory(); + } + + @Override + public void setContents(ItemStack[] items) { + if (getSize() > items.length) { + throw new IllegalArgumentException("Invalid inventory size; expected " + getSize() + " or less"); + } + setContents(items[0], Arrays.copyOfRange(items, 1, items.length)); + } + + @Override + public ItemStack[] getContents() { + ItemStack[] items = new ItemStack[getSize()]; + List mcResultItems = getResultInventory().getContents(); + + int i = 0; + for (i = 0; i < mcResultItems.size(); i++ ) { + items[i] = CraftItemStack.asCraftMirror(mcResultItems.get(i)); + } + + List mcItems = getMatrixInventory().getContents(); + + for (int j = 0; j < mcItems.size(); j++) { + items[i + j] = CraftItemStack.asCraftMirror(mcItems.get(j)); + } + + return items; + } + + public void setContents(ItemStack result, ItemStack[] contents) { + setResult(result); + setMatrix(contents); + } + + @Override + public CraftItemStack getItem(int index) { + if (index < getResultInventory().getSizeInventory()) { + net.minecraft.item.ItemStack item = getResultInventory().getStackInSlot(index); + return item.isEmpty() ? null : CraftItemStack.asCraftMirror(item); + } else { + net.minecraft.item.ItemStack item = getMatrixInventory().getStackInSlot(index - getResultInventory().getSizeInventory()); + return item.isEmpty() ? null : CraftItemStack.asCraftMirror(item); + } + } + + @Override + public void setItem(int index, ItemStack item) { + if (index < getResultInventory().getSizeInventory()) { + getResultInventory().setInventorySlotContents(index, CraftItemStack.asNMSCopy(item)); + } else { + getMatrixInventory().setInventorySlotContents((index - getResultInventory().getSizeInventory()), CraftItemStack.asNMSCopy(item)); + } + } + + public ItemStack[] getMatrix() { + List matrix = getMatrixInventory().getContents(); + + return asCraftMirror(matrix); + } + + public ItemStack getResult() { + net.minecraft.item.ItemStack item = getResultInventory().getStackInSlot(0); + if (!item.isEmpty()) return CraftItemStack.asCraftMirror(item); + return null; + } + + public void setMatrix(ItemStack[] contents) { + if (getMatrixInventory().getSizeInventory() > contents.length) { + throw new IllegalArgumentException("Invalid inventory size; expected " + getMatrixInventory().getSizeInventory() + " or less"); + } + + for (int i = 0; i < getMatrixInventory().getSizeInventory(); i++) { + if (i < contents.length) { + getMatrixInventory().setInventorySlotContents(i, CraftItemStack.asNMSCopy(contents[i])); + } else { + getMatrixInventory().setInventorySlotContents(i, net.minecraft.item.ItemStack.EMPTY); + } + } + } + + public void setResult(ItemStack item) { + List contents = getResultInventory().getContents(); + contents.set(0, CraftItemStack.asNMSCopy(item)); + } + + @Nullable + public Recipe getRecipe() { + IRecipe recipe = ((InventoryCrafting)getInventory()).currentRecipe; + if (recipe != null) { + if (recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe) { + return recipe.toBukkitRecipe(); + } else { + return new CraftCustomModRecipe(recipe); + } + } + return null; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java new file mode 100644 index 00000000..a0e0f361 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java @@ -0,0 +1,236 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.InventoryHolder; + +public class CraftInventoryCustom extends CraftInventory { + public CraftInventoryCustom(InventoryHolder owner, InventoryType type) { + super(new MinecraftInventory(owner, type)); + } + + public CraftInventoryCustom(InventoryHolder owner, InventoryType type, String title) { + super(new MinecraftInventory(owner, type, title)); + } + + public CraftInventoryCustom(InventoryHolder owner, int size) { + super(new MinecraftInventory(owner, size)); + } + + public CraftInventoryCustom(InventoryHolder owner, int size, String title) { + super(new MinecraftInventory(owner, size, title)); + } + + static class MinecraftInventory implements IInventory { + private final NonNullList items; + private int maxStack = MAX_STACK; + private final List viewers; + private final String title; + private InventoryType type; + private final InventoryHolder owner; + + public MinecraftInventory(InventoryHolder owner, InventoryType type) { + this(owner, type.getDefaultSize(), type.getDefaultTitle()); + this.type = type; + } + + public MinecraftInventory(InventoryHolder owner, InventoryType type, String title) { + this(owner, type.getDefaultSize(), title); + this.type = type; + } + + public MinecraftInventory(InventoryHolder owner, int size) { + this(owner, size, "Chest"); + } + + public MinecraftInventory(InventoryHolder owner, int size, String title) { + Validate.notNull(title, "Title cannot be null"); + this.items = NonNullList.withSize(size, ItemStack.EMPTY); + this.title = title; + this.viewers = new ArrayList(); + this.owner = owner; + this.type = InventoryType.CHEST; + } + + @Override + public int getSizeInventory() { + return items.size(); + } + + @Override + public ItemStack getStackInSlot(int i) { + return items.get(i); + } + + @Override + public ItemStack decrStackSize(int i, int j) { + ItemStack stack = this.getStackInSlot(i); + ItemStack result; + if (stack == ItemStack.EMPTY) return stack; + if (stack.getCount() <= j) { + this.setInventorySlotContents(i, ItemStack.EMPTY); + result = stack; + } else { + result = CraftItemStack.copyNMSStack(stack, j); + stack.shrink(j); + } + this.markDirty(); + return result; + } + + @Override + public ItemStack removeStackFromSlot(int i) { + ItemStack stack = this.getStackInSlot(i); + ItemStack result; + if (stack == ItemStack.EMPTY) return stack; + if (stack.getCount() <= 1) { + this.setInventorySlotContents(i, null); + result = stack; + } else { + result = CraftItemStack.copyNMSStack(stack, 1); + stack.shrink(1); + } + return result; + } + + @Override + public void setInventorySlotContents(int i, ItemStack itemstack) { + items.set(i, itemstack); + if (itemstack != ItemStack.EMPTY && this.getInventoryStackLimit() > 0 && itemstack.getCount() > this.getInventoryStackLimit()) { + itemstack.setCount(this.getInventoryStackLimit()); + } + } + + @Override + public int getInventoryStackLimit() { + return maxStack; + } + + public void setMaxStackSize(int size) { + maxStack = size; + } + + @Override + public void markDirty() {} + + @Override + public boolean isUsableByPlayer(EntityPlayer entityhuman) { + return true; + } + + @Override + public void openInventory(EntityPlayer player) { + + } + + @Override + public void closeInventory(EntityPlayer player) { + + } + + @Override + public List getContents() { + return items; + } + + @Override + public void onOpen(CraftHumanEntity who) { + viewers.add(who); + } + + @Override + public void onClose(CraftHumanEntity who) { + viewers.remove(who); + } + + @Override + public List getViewers() { + return viewers; + } + + public InventoryType getType() { + return type; + } + + @Override + public InventoryHolder getOwner() { + return owner; + } + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public int getField(int i) { + return 0; + } + + @Override + public void setField(int i, int j) { + + } + + @Override + public int getFieldCount() { + return 0; + } + + @Override + public void clear() { + items.clear(); + } + + @Override + public String getName() { + return title; + } + + @Override + public boolean hasCustomName() { + return title != null; + } + + @Override + public ITextComponent getDisplayName() { + return new TextComponentString(title); + } + + @Override + public Location getLocation() { + return null; + } + + @Override + public boolean isEmpty() { + Iterator iterator = this.items.iterator(); + + ItemStack itemstack; + + do { + if (!iterator.hasNext()) { + return true; + } + + itemstack = (ItemStack) iterator.next(); + } while (itemstack.isEmpty()); + + return false; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java new file mode 100644 index 00000000..b4f4c51a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java @@ -0,0 +1,67 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.InventoryLargeChest; +import net.minecraft.world.ILockableContainer; +import org.bukkit.block.DoubleChest; +import org.bukkit.inventory.DoubleChestInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import org.bukkit.Location; + +public class CraftInventoryDoubleChest extends CraftInventory implements DoubleChestInventory { + private final CraftInventory left; + private final CraftInventory right; + + public CraftInventoryDoubleChest(CraftInventory left, CraftInventory right) { + super(new InventoryLargeChest("Large chest", (ILockableContainer) left.getInventory(), (ILockableContainer) right.getInventory())); + this.left = left; + this.right = right; + } + + public CraftInventoryDoubleChest(InventoryLargeChest largeChest) { + super(largeChest); + if (largeChest.upperChest instanceof InventoryLargeChest) { + left = new CraftInventoryDoubleChest((InventoryLargeChest) largeChest.upperChest); + } else { + left = new CraftInventory(largeChest.upperChest); + } + if (largeChest.lowerChest instanceof InventoryLargeChest) { + right = new CraftInventoryDoubleChest((InventoryLargeChest) largeChest.lowerChest); + } else { + right = new CraftInventory(largeChest.lowerChest); + } + } + + public Inventory getLeftSide() { + return left; + } + + public Inventory getRightSide() { + return right; + } + + @Override + public void setContents(ItemStack[] items) { + if (getInventory().getSizeInventory() < items.length) { + throw new IllegalArgumentException("Invalid inventory size; expected " + getInventory().getSizeInventory() + " or less"); + } + ItemStack[] leftItems = new ItemStack[left.getSize()], rightItems = new ItemStack[right.getSize()]; + System.arraycopy(items, 0, leftItems, 0, Math.min(left.getSize(), items.length)); + left.setContents(leftItems); + if (items.length >= left.getSize()) { + System.arraycopy(items, left.getSize(), rightItems, 0, Math.min(right.getSize(), items.length - left.getSize())); + right.setContents(rightItems); + } + } + + @Override + public DoubleChest getHolder() { + return new DoubleChest(this); + } + + @Override + public Location getLocation() { + return getLeftSide().getLocation().add(getRightSide().getLocation()).multiply(0.5); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryEnchanting.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryEnchanting.java new file mode 100644 index 00000000..e589143e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryEnchanting.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.IInventory; +import org.bukkit.inventory.EnchantingInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryEnchanting extends CraftInventory implements EnchantingInventory { + public CraftInventoryEnchanting(IInventory inventory) { + super(inventory); + } + + @Override + public void setItem(ItemStack item) { + setItem(0,item); + } + + @Override + public ItemStack getItem() { + return getItem(0); + } + + @Override + public void setSecondary(ItemStack item) { + setItem(1, item); + } + + @Override + public ItemStack getSecondary() { + return getItem(1); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java new file mode 100644 index 00000000..5c6b392a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java @@ -0,0 +1,41 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.tileentity.TileEntityFurnace; +import org.bukkit.block.Furnace; +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryFurnace extends CraftInventory implements FurnaceInventory { + public CraftInventoryFurnace(TileEntityFurnace inventory) { + super(inventory); + } + + public ItemStack getResult() { + return getItem(2); + } + + public ItemStack getFuel() { + return getItem(1); + } + + public ItemStack getSmelting() { + return getItem(0); + } + + public void setFuel(ItemStack stack) { + setItem(1,stack); + } + + public void setResult(ItemStack stack) { + setItem(2,stack); + } + + public void setSmelting(ItemStack stack) { + setItem(0,stack); + } + + @Override + public Furnace getHolder() { + return (Furnace) inventory.getOwner(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java new file mode 100644 index 00000000..b5e96762 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java @@ -0,0 +1,20 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.IInventory; +import org.bukkit.inventory.HorseInventory; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryHorse extends CraftInventoryAbstractHorse implements HorseInventory { + + public CraftInventoryHorse(IInventory inventory) { + super(inventory); + } + + public ItemStack getArmor() { + return getItem(1); + } + + public void setArmor(ItemStack stack) { + setItem(1, stack); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryLlama.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryLlama.java new file mode 100644 index 00000000..020382d5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryLlama.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.IInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.LlamaInventory; + +public class CraftInventoryLlama extends CraftInventoryAbstractHorse implements LlamaInventory { + + public CraftInventoryLlama(IInventory inventory) { + super(inventory); + } + + @Override + public ItemStack getDecor() { + return getItem(1); + } + + @Override + public void setDecor(ItemStack stack) { + setItem(1, stack); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryMerchant.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryMerchant.java new file mode 100644 index 00000000..b5effd6d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryMerchant.java @@ -0,0 +1,27 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.InventoryMerchant; +import org.bukkit.inventory.MerchantInventory; +import org.bukkit.inventory.MerchantRecipe; + +public class CraftInventoryMerchant extends CraftInventory implements MerchantInventory { + + public CraftInventoryMerchant(InventoryMerchant merchant) { + super(merchant); + } + + @Override + public int getSelectedRecipeIndex() { + return getInventory().currentRecipeIndex; + } + + @Override + public MerchantRecipe getSelectedRecipe() { + return getInventory().getCurrentRecipe().asBukkit(); + } + + @Override + public InventoryMerchant getInventory() { + return (InventoryMerchant) inventory; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java new file mode 100644 index 00000000..fa9ee554 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java @@ -0,0 +1,271 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Preconditions; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.network.play.server.SPacketHeldItemChange; +import net.minecraft.network.play.server.SPacketSetSlot; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +public class CraftInventoryPlayer extends CraftInventory implements PlayerInventory, EntityEquipment { + public CraftInventoryPlayer(InventoryPlayer inventory) { + super(inventory); + } + + @Override + public InventoryPlayer getInventory() { + return (InventoryPlayer) inventory; + } + + @Override + public ItemStack[] getStorageContents() { + return asCraftMirror(getInventory().mainInventory); + } + + @Override + public ItemStack getItemInMainHand() { + return CraftItemStack.asCraftMirror(getInventory().getCurrentItem()); + } + + @Override + public void setItemInMainHand(ItemStack item) { + setItem(getHeldItemSlot(), item); + } + + @Override + public ItemStack getItemInOffHand() { + return CraftItemStack.asCraftMirror(getInventory().offHandInventory.get(0)); + } + + @Override + public void setItemInOffHand(ItemStack item) { + ItemStack[] extra = getExtraContents(); + extra[0] = item; + setExtraContents(extra); + } + + @Override + public ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public void setItem(int index, ItemStack item) { + super.setItem(index, item); + if (this.getHolder() == null) return; + EntityPlayerMP player = ((CraftPlayer) this.getHolder()).getHandle(); + if (player.connection == null) return; + // PacketPlayOutSetSlot places the items differently than setItem() + // + // Between, and including, index 9 (the first index outside of the hotbar) and index 35 (the last index before + // armor slots) both PacketPlayOutSetSlot and setItem() places the items in the player's inventory the same way. + // Index 9 starts at the upper left corner of the inventory and moves to the right as it increases. When it + // reaches the end of the line it goes back to the left side of the new line in the inventory. Basically, it + // follows the path your eyes would follow as you read a book. + // + // The player's hotbar is indexed 0-8 in setItem(). The order goes: 0-8 hotbar, 9-35 normal inventory, 36 boots, + // 37 leggings, 38 chestplate, and 39 helmet. For indexes > 39 an ArrayIndexOutOfBoundsException will be thrown. + // + // PacketPlayOutSetSlot works very differently. Slots 0-8 are as follows: 0 crafting output, 1-4 crafting input, + // 5 helmet, 6 chestplate, 7 leggings, and 8 boots. Then, 9-35 work exactly the same as setItem(). The hotbar + // for PacketPlayOutSetSlot starts at index 36, and continues to index 44. Items placed where index is < 0 or + // > 44 have no action. Basically, the upper part of the player's inventory (crafting area and armor slots) is + // the first "row" of 9 slots for PacketPlayOutSetSlot. From there the rows work as normal, from left to right + // all the way down, including the hotbar. + // + // With this in mind, we have to modify the index we give PacketPlayOutSetSlot to match the index we intended + // with setItem(). First, if the index is 0-8, we need to add 36, or 4 rows worth of slots, to the index. This + // will push the item down to the correct spot in the hotbar. + // + // Now when index is > 35 (if index > 39 an ArrayIndexOutOfBoundsException will be thrown, so we need not worry + // about it) then we need to reset the index, and then count backwards from the "top" of the inventory. That is + // to say, we first find (index - 36), which will give us the index required for the armor slots. Now, we need + // to reverse the order of the index from 8. That means we need 0 to correspond to 8, 1 to correspond to 7, + // 2 to correspond to 6, and 3 to correspond to 5. We do this simply by taking the result of (index - 36) and + // subtracting that value from 8. + if (index < InventoryPlayer.getHotbarSize()) { + index += 36; + } else if (index > 39) { + index += 5; // Off hand + } else if (index > 35) { + index = 8 - (index - 36); + } + player.connection.sendPacket(new SPacketSetSlot(player.inventoryContainer.windowId, index, CraftItemStack.asNMSCopy(item))); + } + + public int getHeldItemSlot() { + return getInventory().currentItem; + } + + public void setHeldItemSlot(int slot) { + Validate.isTrue(slot >= 0 && slot < InventoryPlayer.getHotbarSize(), "Slot is not between 0 and 8 inclusive"); + this.getInventory().currentItem = slot; + ((CraftPlayer) this.getHolder()).getHandle().connection.sendPacket(new SPacketHeldItemChange(slot)); + } + + public ItemStack getHelmet() { + return getItem(getSize() - 2); + } + + public ItemStack getChestplate() { + return getItem(getSize() - 3); + } + + public ItemStack getLeggings() { + return getItem(getSize() - 4); + } + + public ItemStack getBoots() { + return getItem(getSize() - 5); + } + + public void setHelmet(ItemStack helmet) { + setItem(getSize() - 2, helmet); + } + + public void setChestplate(ItemStack chestplate) { + setItem(getSize() - 3, chestplate); + } + + public void setLeggings(ItemStack leggings) { + setItem(getSize() - 4, leggings); + } + + public void setBoots(ItemStack boots) { + setItem(getSize() - 5, boots); + } + + public ItemStack[] getArmorContents() { + return asCraftMirror(getInventory().armorInventory); + } + + private void setSlots(ItemStack[] items, int baseSlot, int length) { + if (items == null) { + items = new ItemStack[length]; + } + Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length); + + for (int i = 0; i < length; i++) { + if (i >= items.length) { + setItem(baseSlot + i, null); + } else { + setItem(baseSlot + i, items[i]); + } + } + } + + @Override + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + setSlots(items, 0, getInventory().mainInventory.size()); + } + + @Override + public void setArmorContents(ItemStack[] items) { + setSlots(items, getInventory().mainInventory.size(), getInventory().armorInventory.size()); + } + + @Override + public ItemStack[] getExtraContents() { + return asCraftMirror(getInventory().offHandInventory); + } + + @Override + public void setExtraContents(ItemStack[] items) { + setSlots(items, getInventory().mainInventory.size() + getInventory().armorInventory.size(), getInventory().offHandInventory.size()); + } + + public int clear(int id, int data) { + int count = 0; + ItemStack[] items = getContents(); + + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + if (item == null) continue; + if (id > -1 && item.getTypeId() != id) continue; + if (data > -1 && item.getData().getData() != data) continue; + + count += item.getAmount(); + setItem(i, null); + } + + return count; + } + + @Override + public HumanEntity getHolder() { + return (HumanEntity) inventory.getOwner(); + } + + @Override + public float getItemInHandDropChance() { + return getItemInMainHandDropChance(); + } + + @Override + public void setItemInHandDropChance(float chance) { + setItemInMainHandDropChance(chance); + } + + @Override + public float getItemInMainHandDropChance() { + return 1; + } + + @Override + public void setItemInMainHandDropChance(float chance) { + throw new UnsupportedOperationException(); + } + + @Override + public float getItemInOffHandDropChance() { + return 1; + } + + @Override + public void setItemInOffHandDropChance(float chance) { + throw new UnsupportedOperationException(); + } + + public float getHelmetDropChance() { + return 1; + } + + public void setHelmetDropChance(float chance) { + throw new UnsupportedOperationException(); + } + + public float getChestplateDropChance() { + return 1; + } + + public void setChestplateDropChance(float chance) { + throw new UnsupportedOperationException(); + } + + public float getLeggingsDropChance() { + return 1; + } + + public void setLeggingsDropChance(float chance) { + throw new UnsupportedOperationException(); + } + + public float getBootsDropChance() { + return 1; + } + + public void setBootsDropChance(float chance) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java new file mode 100644 index 00000000..8052563d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java @@ -0,0 +1,141 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.inventory.Container; +import org.bukkit.GameMode; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +public class CraftInventoryView extends InventoryView { + private final Container container; + private final CraftHumanEntity player; + private final CraftInventory viewing; + + public CraftInventoryView(HumanEntity player, Inventory viewing, Container container) { + // TODO: Should we make sure it really IS a CraftHumanEntity first? And a CraftInventory? + this.player = (CraftHumanEntity) player; + this.viewing = (CraftInventory) viewing; + this.container = container; + } + + @Override + public Inventory getTopInventory() { + return viewing; + } + + @Override + public Inventory getBottomInventory() { + return player.getInventory(); + } + + @Override + public HumanEntity getPlayer() { + return player; + } + + @Override + public InventoryType getType() { + InventoryType type = viewing.getType(); + if (type == InventoryType.CRAFTING && player.getGameMode() == GameMode.CREATIVE) { + return InventoryType.CREATIVE; + } + return type; + } + + @Override + public void setItem(int slot, ItemStack item) { + net.minecraft.item.ItemStack stack = CraftItemStack.asNMSCopy(item); + if (slot != -999) { + container.getSlot(slot).putStack(stack); + } else { + player.getHandle().dropItem(stack, false); + } + } + + @Override + public ItemStack getItem(int slot) { + if (slot == -999) { + return null; + } + return CraftItemStack.asCraftMirror(container.getSlot(slot).getStack()); + } + + public boolean isInTop(int rawSlot) { + return rawSlot < viewing.getSize(); + } + + public Container getHandle() { + return container; + } + + public static SlotType getSlotType(InventoryView inventory, int slot) { + SlotType type = SlotType.CONTAINER; + if (slot >= 0 && slot < inventory.getTopInventory().getSize()) { + switch(inventory.getType()) { + case FURNACE: + if (slot == 2) { + type = SlotType.RESULT; + } else if(slot == 1) { + type = SlotType.FUEL; + } else { + type = SlotType.CRAFTING; + } + break; + case BREWING: + if (slot == 3) { + type = SlotType.FUEL; + } else { + type = SlotType.CRAFTING; + } + break; + case ENCHANTING: + type = SlotType.CRAFTING; + break; + case WORKBENCH: + case CRAFTING: + if (slot == 0) { + type = SlotType.RESULT; + } else { + type = SlotType.CRAFTING; + } + break; + case MERCHANT: + if (slot == 2) { + type = SlotType.RESULT; + } else { + type = SlotType.CRAFTING; + } + break; + case BEACON: + type = SlotType.CRAFTING; + break; + case ANVIL: + if (slot == 2) { + type = SlotType.RESULT; + } else { + type = SlotType.CRAFTING; + } + break; + default: + // Nothing to do, it's a CONTAINER slot + } + } else { + if (slot == -999 || slot == -1) { + type = SlotType.OUTSIDE; + } else if (inventory.getType() == InventoryType.CRAFTING) { // Also includes creative inventory + if (slot < 9) { + type = SlotType.ARMOR; + } else if (slot > 35) { + type = SlotType.QUICKBAR; + } + } else if (slot >= (inventory.countSlots() - (9 + 4 + 1))) { // Quickbar, Armor, Offhand + type = SlotType.QUICKBAR; + } + } + return type; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java new file mode 100644 index 00000000..0c57c67c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java @@ -0,0 +1,199 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Collection; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.google.common.collect.ImmutableSet; + +public final class CraftItemFactory implements ItemFactory { + static final Color DEFAULT_LEATHER_COLOR = Color.fromRGB(0xA06540); + static final Collection KNOWN_NBT_ATTRIBUTE_NAMES; + private static final CraftItemFactory instance; + + static { + instance = new CraftItemFactory(); + ConfigurationSerialization.registerClass(CraftMetaItem.SerializableMeta.class); + KNOWN_NBT_ATTRIBUTE_NAMES = ImmutableSet.builder() + .add("generic.armor") + .add("generic.armorToughness") + .add("generic.attackDamage") + .add("generic.followRange") + .add("generic.knockbackResistance") + .add("generic.maxHealth") + .add("generic.movementSpeed") + .add("generic.flyingSpeed") + .add("generic.attackSpeed") + .add("generic.luck") + .add("horse.jumpStrength") + .add("zombie.spawnReinforcements") + .add("generic.reachDistance") + .add("forge.swimSpeed") + .build(); + } + + private CraftItemFactory() { + } + + public boolean isApplicable(ItemMeta meta, ItemStack itemstack) { + if (itemstack == null) { + return false; + } + return isApplicable(meta, itemstack.getType()); + } + + public boolean isApplicable(ItemMeta meta, Material type) { + if (type == null || meta == null) { + return false; + } + if (!(meta instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Meta of " + meta.getClass().toString() + " not created by " + CraftItemFactory.class.getName()); + } + + return ((CraftMetaItem) meta).applicableTo(type); + } + + public ItemMeta getItemMeta(Material material) { + Validate.notNull(material, "Material cannot be null"); + return getItemMeta(material, null); + } + + private ItemMeta getItemMeta(Material material, CraftMetaItem meta) { + switch (material) { + case AIR: + return null; + case WRITTEN_BOOK: + return meta instanceof CraftMetaBookSigned ? meta : new CraftMetaBookSigned(meta); + case BOOK_AND_QUILL: + return meta != null && meta.getClass().equals(CraftMetaBook.class) ? meta : new CraftMetaBook(meta); + case SKULL_ITEM: + return meta instanceof CraftMetaSkull ? meta : new CraftMetaSkull(meta); + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return meta instanceof CraftMetaLeatherArmor ? meta : new CraftMetaLeatherArmor(meta); + case POTION: + case SPLASH_POTION: + case LINGERING_POTION: + case TIPPED_ARROW: + return meta instanceof CraftMetaPotion ? meta : new CraftMetaPotion(meta); + case MAP: + return meta instanceof CraftMetaMap ? meta : new CraftMetaMap(meta); + case FIREWORK: + return meta instanceof CraftMetaFirework ? meta : new CraftMetaFirework(meta); + case FIREWORK_CHARGE: + return meta instanceof CraftMetaCharge ? meta : new CraftMetaCharge(meta); + case ENCHANTED_BOOK: + return meta instanceof CraftMetaEnchantedBook ? meta : new CraftMetaEnchantedBook(meta); + case BANNER: + return meta instanceof CraftMetaBanner ? meta : new CraftMetaBanner(meta); + case MONSTER_EGG: + return meta instanceof CraftMetaSpawnEgg ? meta : new CraftMetaSpawnEgg(meta); + case KNOWLEDGE_BOOK: + return meta instanceof CraftMetaKnowledgeBook ? meta : new CraftMetaKnowledgeBook(meta); + case FURNACE: + case CHEST: + case TRAPPED_CHEST: + case JUKEBOX: + case DISPENSER: + case DROPPER: + case SIGN: + case MOB_SPAWNER: + case NOTE_BLOCK: + case BREWING_STAND_ITEM: + case ENCHANTMENT_TABLE: + case COMMAND: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + case BEACON: + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + case HOPPER: + case REDSTONE_COMPARATOR: + case FLOWER_POT_ITEM: + case SHIELD: + case STRUCTURE_BLOCK: + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + case ENDER_CHEST: + return new CraftMetaBlockState(meta, material); + default: + return new CraftMetaItem(meta); + } + } + + public boolean equals(ItemMeta meta1, ItemMeta meta2) { + if (meta1 == meta2) { + return true; + } + if (meta1 != null && !(meta1 instanceof CraftMetaItem)) { + throw new IllegalArgumentException("First meta of " + meta1.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName()); + } + if (meta2 != null && !(meta2 instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Second meta " + meta2.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName()); + } + if (meta1 == null) { + return ((CraftMetaItem) meta2).isEmpty(); + } + if (meta2 == null) { + return ((CraftMetaItem) meta1).isEmpty(); + } + + return equals((CraftMetaItem) meta1, (CraftMetaItem) meta2); + } + + boolean equals(CraftMetaItem meta1, CraftMetaItem meta2) { + /* + * This couldn't be done inside of the objects themselves, else force recursion. + * This is a fairly clean way of implementing it, by dividing the methods into purposes and letting each method perform its own function. + * + * The common and uncommon were split, as both could have variables not applicable to the other, like a skull and book. + * Each object needs its chance to say "hey wait a minute, we're not equal," but without the redundancy of using the 1.equals(2) && 2.equals(1) checking the 'commons' twice. + * + * Doing it this way fills all conditions of the .equals() method. + */ + return meta1.equalsCommon(meta2) && meta1.notUncommon(meta2) && meta2.notUncommon(meta1); + } + + public static CraftItemFactory instance() { + return instance; + } + + public ItemMeta asMetaFor(ItemMeta meta, ItemStack stack) { + Validate.notNull(stack, "Stack cannot be null"); + return asMetaFor(meta, stack.getType()); + } + + public ItemMeta asMetaFor(ItemMeta meta, Material material) { + Validate.notNull(material, "Material cannot be null"); + if (!(meta instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Meta of " + (meta != null ? meta.getClass().toString() : "null") + " not created by " + CraftItemFactory.class.getName()); + } + return getItemMeta(material, (CraftMetaItem) meta); + } + + public Color getDefaultLeatherColor() { + return DEFAULT_LEATHER_COLOR; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java new file mode 100644 index 00000000..a54d9d1d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -0,0 +1,470 @@ +package org.bukkit.craftbukkit.inventory; + +import static org.bukkit.craftbukkit.inventory.CraftMetaItem.ENCHANTMENTS; +import static org.bukkit.craftbukkit.inventory.CraftMetaItem.ENCHANTMENTS_ID; +import static org.bukkit.craftbukkit.inventory.CraftMetaItem.ENCHANTMENTS_LVL; + +import java.util.Map; + +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.item.Item; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.google.common.collect.ImmutableMap; +import org.bukkit.craftbukkit.enchantments.CraftEnchantment; + +@DelegateDeserialization(ItemStack.class) +public final class CraftItemStack extends ItemStack { + + public static net.minecraft.item.ItemStack asNMSCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return stack.handle == null ? net.minecraft.item.ItemStack.EMPTY : stack.handle.copy(); + } + if (original == null || original.getTypeId() <= 0) { + return net.minecraft.item.ItemStack.EMPTY; + } + + Item item = CraftMagicNumbers.getItem(original.getType()); + + if (item == null) { + return net.minecraft.item.ItemStack.EMPTY; + } + + net.minecraft.item.ItemStack stack = new net.minecraft.item.ItemStack(item, original.getAmount(), original.getDurability()); + if (original.hasItemMeta()) { + setItemMeta(stack, original.getItemMeta()); + } + return stack; + } + + public static net.minecraft.item.ItemStack copyNMSStack(net.minecraft.item.ItemStack original, int amount) { + net.minecraft.item.ItemStack stack = original.copy(); + stack.setCount(amount); + return stack; + } + + /** + * Copies the NMS stack to return as a strictly-Bukkit stack + */ + public static ItemStack asBukkitCopy(net.minecraft.item.ItemStack original) { + if (original.isEmpty()) { + return new ItemStack(Material.AIR); + } + // Kettle start - don't use strict Bukkit stacks as we don't have Bukkit Materials for modded item stacks, create wrapper? (Cauldron) + return asCraftMirror(copyNMSStack(original, original.getCount())); + /* + ItemStack stack = new ItemStack(CraftMagicNumbers.getMaterial(original.getItem()), original.getCount(), (short) original.getMetadata()); + if (hasItemMeta(original)) { + stack.setItemMeta(getItemMeta(original)); + } + return stack; + */ + } + + public static CraftItemStack asCraftMirror(net.minecraft.item.ItemStack original) { + return new CraftItemStack((original == null || original.isEmpty()) ? null : original); + } + + public static CraftItemStack asCraftCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return new CraftItemStack(stack.handle == null ? null : stack.handle.copy()); + } + return new CraftItemStack(original); + } + + public static CraftItemStack asNewCraftStack(Item item) { + return asNewCraftStack(item, 1); + } + + public static CraftItemStack asNewCraftStack(Item item, int amount) { + return new CraftItemStack(CraftMagicNumbers.getMaterial(item), amount, (short) 0, null); + } + + net.minecraft.item.ItemStack handle; + + /** + * Mirror + */ + private CraftItemStack(net.minecraft.item.ItemStack item) { + this.handle = item; + } + + private CraftItemStack(ItemStack item) { + this(item.getTypeId(), item.getAmount(), item.getDurability(), item.hasItemMeta() ? item.getItemMeta() : null); + } + + private CraftItemStack(Material type, int amount, short durability, ItemMeta itemMeta) { + setType(type); + setAmount(amount); + setDurability(durability); + setItemMeta(itemMeta); + } + + private CraftItemStack(int typeId, int amount, short durability, ItemMeta itemMeta) { + this(Material.getMaterial(typeId), amount, durability, itemMeta); + + } + + @Override + public int getTypeId() { + return handle != null ? CraftMagicNumbers.getId(handle.getItem()) : 0; + } + + @Override + public void setTypeId(int type) { + if (getTypeId() == type) { + return; + } else if (type == 0) { + handle = null; + } else if (CraftMagicNumbers.getItem(type) == null) { // :( + handle = null; + } else if (handle == null) { + handle = new net.minecraft.item.ItemStack(CraftMagicNumbers.getItem(type), 1, 0); + } else { + handle.setItem(CraftMagicNumbers.getItem(type)); + if (hasItemMeta()) { + // This will create the appropriate item meta, which will contain all the data we intend to keep + setItemMeta(handle, getItemMeta(handle)); + } + } + setData(null); + } + + @Override + public int getAmount() { + return handle != null ? handle.getCount() : 0; + } + + @Override + public void setAmount(int amount) { + if (handle == null) { + return; + } + + handle.setCount(amount); + if (amount == 0) { + handle = null; + } + } + + @Override + public void setDurability(final short durability) { + // Ignore damage if item is null + if (handle != null) { + handle.setItemDamage(durability); + } + } + + @Override + public short getDurability() { + if (handle != null) { + return (short) handle.getMetadata(); + } else { + return -1; + } + } + + @Override + public int getMaxStackSize() { + return (handle == null) ? Material.AIR.getMaxStackSize() : handle.getItem().getItemStackLimit(); + } + + @Override + public void addUnsafeEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Cannot add null enchantment"); + + if (!makeTag(handle)) { + return; + } + NBTTagList list = getEnchantmentList(handle); + if (list == null) { + list = new NBTTagList(); + handle.getTagCompound().setTag(ENCHANTMENTS.NBT, list); + } + int size = list.tagCount(); + + for (int i = 0; i < size; i++) { + NBTTagCompound tag = (NBTTagCompound) list.get(i); + short id = tag.getShort(ENCHANTMENTS_ID.NBT); + if (id == ench.getId()) { + tag.setShort(ENCHANTMENTS_LVL.NBT, (short) level); + return; + } + } + NBTTagCompound tag = new NBTTagCompound(); + tag.setShort(ENCHANTMENTS_ID.NBT, (short) ench.getId()); + tag.setShort(ENCHANTMENTS_LVL.NBT, (short) level); + list.appendTag(tag); + } + + static boolean makeTag(net.minecraft.item.ItemStack item) { + if (item == null) { + return false; + } + + if (item.getTagCompound() == null) { + item.setTagCompound(new NBTTagCompound()); + } + + return true; + } + + @Override + public boolean containsEnchantment(Enchantment ench) { + return getEnchantmentLevel(ench) > 0; + } + + @Override + public int getEnchantmentLevel(Enchantment ench) { + Validate.notNull(ench, "Cannot find null enchantment"); + if (handle == null) { + return 0; + } + return EnchantmentHelper.getEnchantmentLevel(CraftEnchantment.getRaw(ench), handle); + } + + @Override + public int removeEnchantment(Enchantment ench) { + Validate.notNull(ench, "Cannot remove null enchantment"); + + NBTTagList list = getEnchantmentList(handle), listCopy; + if (list == null) { + return 0; + } + int index = Integer.MIN_VALUE; + int level = Integer.MIN_VALUE; + int size = list.tagCount(); + + for (int i = 0; i < size; i++) { + NBTTagCompound enchantment = (NBTTagCompound) list.get(i); + int id = 0xffff & enchantment.getShort(ENCHANTMENTS_ID.NBT); + if (id == ench.getId()) { + index = i; + level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); + break; + } + } + + if (index == Integer.MIN_VALUE) { + return 0; + } + if (size == 1) { + handle.getTagCompound().removeTag(ENCHANTMENTS.NBT); + if (handle.getTagCompound().hasNoTags()) { + handle.setTagCompound(null); + } + return level; + } + + // This is workaround for not having an index removal + listCopy = new NBTTagList(); + for (int i = 0; i < size; i++) { + if (i != index) { + listCopy.appendTag(list.get(i)); + } + } + handle.getTagCompound().setTag(ENCHANTMENTS.NBT, listCopy); + + return level; + } + + @Override + public Map getEnchantments() { + return getEnchantments(handle); + } + + static Map getEnchantments(net.minecraft.item.ItemStack item) { + NBTTagList list = (item != null && item.isItemEnchanted()) ? item.getEnchantmentTagList() : null; + + if (list == null || list.tagCount() == 0) { + return ImmutableMap.of(); + } + + ImmutableMap.Builder result = ImmutableMap.builder(); + + for (int i = 0; i < list.tagCount(); i++) { + int id = 0xffff & ((NBTTagCompound) list.get(i)).getShort(ENCHANTMENTS_ID.NBT); + int level = 0xffff & ((NBTTagCompound) list.get(i)).getShort(ENCHANTMENTS_LVL.NBT); + + result.put(Enchantment.getById(id), level); + } + + return result.build(); + } + + static NBTTagList getEnchantmentList(net.minecraft.item.ItemStack item) { + return (item != null && item.isItemEnchanted()) ? item.getEnchantmentTagList() : null; + } + + @Override + public CraftItemStack clone() { + CraftItemStack itemStack = (CraftItemStack) super.clone(); + if (this.handle != null) { + itemStack.handle = this.handle.copy(); + } + return itemStack; + } + + @Override + public ItemMeta getItemMeta() { + return getItemMeta(handle); + } + + public static ItemMeta getItemMeta(net.minecraft.item.ItemStack item) { + if (!hasItemMeta(item)) { + return CraftItemFactory.instance().getItemMeta(getType(item)); + } + switch (getType(item)) { + case WRITTEN_BOOK: + return new CraftMetaBookSigned(item.getTagCompound()); + case BOOK_AND_QUILL: + return new CraftMetaBook(item.getTagCompound()); + case SKULL_ITEM: + return new CraftMetaSkull(item.getTagCompound()); + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return new CraftMetaLeatherArmor(item.getTagCompound()); + case POTION: + case SPLASH_POTION: + case LINGERING_POTION: + case TIPPED_ARROW: + return new CraftMetaPotion(item.getTagCompound()); + case MAP: + return new CraftMetaMap(item.getTagCompound()); + case FIREWORK: + return new CraftMetaFirework(item.getTagCompound()); + case FIREWORK_CHARGE: + return new CraftMetaCharge(item.getTagCompound()); + case ENCHANTED_BOOK: + return new CraftMetaEnchantedBook(item.getTagCompound()); + case BANNER: + return new CraftMetaBanner(item.getTagCompound()); + case MONSTER_EGG: + return new CraftMetaSpawnEgg(item.getTagCompound()); + case KNOWLEDGE_BOOK: + return new CraftMetaKnowledgeBook(item.getTagCompound()); + case FURNACE: + case CHEST: + case TRAPPED_CHEST: + case JUKEBOX: + case DISPENSER: + case DROPPER: + case SIGN: + case MOB_SPAWNER: + case NOTE_BLOCK: + case BREWING_STAND_ITEM: + case ENCHANTMENT_TABLE: + case COMMAND: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + case BEACON: + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + case HOPPER: + case REDSTONE_COMPARATOR: + case FLOWER_POT_ITEM: + case SHIELD: + case STRUCTURE_BLOCK: + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + case ENDER_CHEST: + return new CraftMetaBlockState(item.getTagCompound(), CraftMagicNumbers.getMaterial(item.getItem())); + default: + return new CraftMetaItem(item.getTagCompound()); + } + } + + static Material getType(net.minecraft.item.ItemStack item) { + Material material = Material.getMaterial(item == null ? 0 : CraftMagicNumbers.getId(item.getItem())); + return material == null ? Material.AIR : material; + } + + @Override + public boolean setItemMeta(ItemMeta itemMeta) { + return setItemMeta(handle, itemMeta); + } + + public static boolean setItemMeta(net.minecraft.item.ItemStack item, ItemMeta itemMeta) { + if (item == null) { + return false; + } + if (CraftItemFactory.instance().equals(itemMeta, null)) { + item.setTagCompound(null); + return true; + } + if (!CraftItemFactory.instance().isApplicable(itemMeta, getType(item))) { + return false; + } + + itemMeta = CraftItemFactory.instance().asMetaFor(itemMeta, getType(item)); + if (itemMeta == null) return true; + + NBTTagCompound tag = new NBTTagCompound(); + item.setTagCompound(tag); + + ((CraftMetaItem) itemMeta).applyToItem(tag); + + return true; + } + + @Override + public boolean isSimilar(ItemStack stack) { + if (stack == null) { + return false; + } + if (stack == this) { + return true; + } + if (!(stack instanceof CraftItemStack)) { + return stack.getClass() == ItemStack.class && stack.isSimilar(this); + } + + CraftItemStack that = (CraftItemStack) stack; + if (handle == that.handle) { + return true; + } + if (handle == null || that.handle == null) { + return false; + } + if (!(that.getTypeId() == getTypeId() && getDurability() == that.getDurability())) { + return false; + } + return hasItemMeta() ? that.hasItemMeta() && handle.getTagCompound().equals(that.handle.getTagCompound()) : !that.hasItemMeta(); + } + + @Override + public boolean hasItemMeta() { + return hasItemMeta(handle); + } + + static boolean hasItemMeta(net.minecraft.item.ItemStack item) { + return !(item == null || item.getTagCompound() == null || item.getTagCompound().hasNoTags()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java new file mode 100644 index 00000000..60b773b3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java @@ -0,0 +1,80 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.collect.Lists; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.IMerchant; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.village.MerchantRecipeList; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.MerchantRecipe; + +public class CraftMerchant implements Merchant { + + protected final IMerchant merchant; + + public CraftMerchant(IMerchant merchant) { + this.merchant = merchant; + } + + public IMerchant getMerchant() { + return merchant; + } + + @Override + public List getRecipes() { + return Collections.unmodifiableList(Lists.transform(merchant.getRecipes(null), new com.google.common.base.Function() { + @Override + public MerchantRecipe apply(net.minecraft.village.MerchantRecipe recipe) { + return recipe.asBukkit(); + } + })); + } + + @Override + public void setRecipes(List recipes) { + MerchantRecipeList recipesList = merchant.getRecipes(null); + recipesList.clear(); + for (MerchantRecipe recipe : recipes) { + recipesList.add(CraftMerchantRecipe.fromBukkit(recipe).toMinecraft()); + } + } + + @Override + public MerchantRecipe getRecipe(int i) { + return merchant.getRecipes(null).get(i).asBukkit(); + } + + @Override + public void setRecipe(int i, MerchantRecipe merchantRecipe) { + merchant.getRecipes(null).set(i, CraftMerchantRecipe.fromBukkit(merchantRecipe).toMinecraft()); + } + + @Override + public int getRecipeCount() { + return merchant.getRecipes(null).size(); + } + + @Override + public boolean isTrading() { + return getTrader() != null; + } + + @Override + public HumanEntity getTrader() { + EntityPlayer eh = merchant.getCustomer(); + return eh == null ? null : eh.getBukkitEntity(); + } + + @Override + public int hashCode() { + return merchant.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof CraftMerchant && ((CraftMerchant) obj).merchant.equals(this.merchant); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java new file mode 100644 index 00000000..a9be18d2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java @@ -0,0 +1,85 @@ +package org.bukkit.craftbukkit.inventory; + +import net.minecraft.entity.IMerchant; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.village.MerchantRecipe; +import net.minecraft.village.MerchantRecipeList; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; + +public class CraftMerchantCustom extends CraftMerchant { + + public CraftMerchantCustom(String title) { + super(new MinecraftMerchant(title)); + } + + @Override + public String toString() { + return "CraftMerchantCustom"; + } + + private static class MinecraftMerchant implements IMerchant { + + private final String title; + private final MerchantRecipeList trades = new MerchantRecipeList(); + private EntityPlayer tradingPlayer; + + public MinecraftMerchant(String title) { + this.title = title; + } + + @Override + public void setCustomer(EntityPlayer entityhuman) { + this.tradingPlayer = entityhuman; + } + + @Override + public EntityPlayer getCustomer() { + return this.tradingPlayer; + } + + @Override + public MerchantRecipeList getRecipes(EntityPlayer entityhuman) { + return this.trades; + } + + @Override + @SideOnly(Side.CLIENT) + public void setRecipes(@Nullable MerchantRecipeList recipeList) { + + } + + @Override + public void useRecipe(MerchantRecipe merchantrecipe) { + // increase recipe's uses + merchantrecipe.incrementToolUses(); + } + + @Override + public void verifySellingItem(ItemStack itemstack) { + + } + + @Override + public ITextComponent getDisplayName() { + return new TextComponentString(title); + } + + @Override + public World getWorld() { + return null; + } + + @Override + public BlockPos getPos() { + return null; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java new file mode 100644 index 00000000..bb626955 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java @@ -0,0 +1,81 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.List; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; + +public class CraftMerchantRecipe extends MerchantRecipe { + + private final net.minecraft.village.MerchantRecipe handle; + + public CraftMerchantRecipe(net.minecraft.village.MerchantRecipe merchantRecipe) { + super(CraftItemStack.asBukkitCopy(merchantRecipe.itemToSell), 0); + this.handle = merchantRecipe; + addIngredient(CraftItemStack.asBukkitCopy(merchantRecipe.itemToBuy)); + addIngredient(CraftItemStack.asBukkitCopy(merchantRecipe.secondItemToBuy)); + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward) { + super(result, uses, maxUses, experienceReward); + this.handle = new net.minecraft.village.MerchantRecipe( + net.minecraft.item.ItemStack.EMPTY, + net.minecraft.item.ItemStack.EMPTY, + CraftItemStack.asNMSCopy(result), + uses, + maxUses, + this + ); + } + + @Override + public int getUses() { + return handle.toolUses; + } + + @Override + public void setUses(int uses) { + handle.toolUses = uses; + } + + @Override + public int getMaxUses() { + return handle.maxTradeUses; + } + + @Override + public void setMaxUses(int maxUses) { + handle.maxTradeUses = maxUses; + } + + @Override + public boolean hasExperienceReward() { + return handle.rewardsExp; + } + + @Override + public void setExperienceReward(boolean flag) { + handle.rewardsExp = flag; + } + + public net.minecraft.village.MerchantRecipe toMinecraft() { + List ingredients = getIngredients(); + Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); + handle.itemToBuy = CraftItemStack.asNMSCopy(ingredients.get(0)); + if (ingredients.size() > 1) { + handle.secondItemToBuy = CraftItemStack.asNMSCopy(ingredients.get(1)); + } + return handle; + } + + public static CraftMerchantRecipe fromBukkit(MerchantRecipe recipe) { + if (recipe instanceof CraftMerchantRecipe) { + return (CraftMerchantRecipe) recipe; + } else { + CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward()); + craft.setIngredients(recipe.getIngredients()); + + return craft; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java new file mode 100644 index 00000000..c2d618b8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java @@ -0,0 +1,210 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.BannerMeta; + +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { + + static final ItemMetaKey BASE = new ItemMetaKey("Base", "base-color"); + static final ItemMetaKey PATTERNS = new ItemMetaKey("Patterns", "patterns"); + static final ItemMetaKey COLOR = new ItemMetaKey("Color", "color"); + static final ItemMetaKey PATTERN = new ItemMetaKey("Pattern", "pattern"); + + private DyeColor base; + private List patterns = new ArrayList(); + + CraftMetaBanner(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaBanner)) { + return; + } + + CraftMetaBanner banner = (CraftMetaBanner) meta; + base = banner.base; + patterns = new ArrayList(banner.patterns); + } + + CraftMetaBanner(NBTTagCompound tag) { + super(tag); + + if (!tag.hasKey("BlockEntityTag")) { + return; + } + + NBTTagCompound entityTag = tag.getCompoundTag("BlockEntityTag"); + + base = entityTag.hasKey(BASE.NBT) ? DyeColor.getByDyeData((byte) entityTag.getInteger(BASE.NBT)) : null; + + if (entityTag.hasKey(PATTERNS.NBT)) { + NBTTagList patterns = entityTag.getTagList(PATTERNS.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + for (int i = 0; i < Math.min(patterns.tagCount(), 20); i++) { + NBTTagCompound p = patterns.getCompoundTagAt(i); + this.patterns.add(new Pattern(DyeColor.getByDyeData((byte) p.getInteger(COLOR.NBT)), PatternType.getByIdentifier(p.getString(PATTERN.NBT)))); + } + } + } + + CraftMetaBanner(Map map) { + super(map); + + String baseStr = SerializableMeta.getString(map, BASE.BUKKIT, true); + if (baseStr != null) { + base = DyeColor.valueOf(baseStr); + } + + Iterable rawPatternList = SerializableMeta.getObject(Iterable.class, map, PATTERNS.BUKKIT, true); + if (rawPatternList == null) { + return; + } + + for (Object obj : rawPatternList) { + if (!(obj instanceof Pattern)) { + throw new IllegalArgumentException("Object in pattern list is not valid. " + obj.getClass()); + } + addPattern((Pattern) obj); + } + } + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + NBTTagCompound entityTag = new NBTTagCompound(); + if (base != null) { + entityTag.setInteger(BASE.NBT, base.getDyeData()); + } + + NBTTagList newPatterns = new NBTTagList(); + + for (Pattern p : patterns) { + NBTTagCompound compound = new NBTTagCompound(); + compound.setInteger(COLOR.NBT, p.getColor().getDyeData()); + compound.setString(PATTERN.NBT, p.getPattern().getIdentifier()); + newPatterns.appendTag(compound); + } + entityTag.setTag(PATTERNS.NBT, newPatterns); + + tag.setTag("BlockEntityTag", entityTag); + } + + @Override + public DyeColor getBaseColor() { + return base; + } + + @Override + public void setBaseColor(DyeColor color) { + base = color; + } + + @Override + public List getPatterns() { + return new ArrayList(patterns); + } + + @Override + public void setPatterns(List patterns) { + this.patterns = new ArrayList(patterns); + } + + @Override + public void addPattern(Pattern pattern) { + patterns.add(pattern); + } + + @Override + public Pattern getPattern(int i) { + return patterns.get(i); + } + + @Override + public Pattern removePattern(int i) { + return patterns.remove(i); + } + + @Override + public void setPattern(int i, Pattern pattern) { + patterns.set(i, pattern); + } + + @Override + public int numberOfPatterns() { + return patterns.size(); + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + + if(base != null){ + builder.put(BASE.BUKKIT, base.toString()); + } + + if(!patterns.isEmpty()){ + builder.put(PATTERNS.BUKKIT, ImmutableList.copyOf(patterns)); + } + + return builder; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (base != null) { + hash = 31 * hash + base.hashCode(); + } + if (!patterns.isEmpty()) { + hash = 31 * hash + patterns.hashCode(); + } + return original != hash ? CraftMetaBanner.class.hashCode() ^ hash : hash; + } + + @Override + public boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaBanner) { + CraftMetaBanner that = (CraftMetaBanner) meta; + + return base == that.base && patterns.equals(that.patterns); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaBanner || (patterns.isEmpty() && base == null)); + } + + @Override + boolean isEmpty() { + return super.isEmpty() && patterns.isEmpty() && base == null; + } + + @Override + boolean applicableTo(Material type) { + return type == Material.BANNER; + } + + @Override + public CraftMetaBanner clone() { + CraftMetaBanner meta = (CraftMetaBanner) super.clone(); + meta.patterns = new ArrayList<>(patterns); + return meta; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java new file mode 100644 index 00000000..309064c6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java @@ -0,0 +1,508 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +import net.minecraft.block.BlockJukebox; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityBanner; +import net.minecraft.tileentity.TileEntityBeacon; +import net.minecraft.tileentity.TileEntityBrewingStand; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.tileentity.TileEntityCommandBlock; +import net.minecraft.tileentity.TileEntityComparator; +import net.minecraft.tileentity.TileEntityDaylightDetector; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.tileentity.TileEntityDropper; +import net.minecraft.tileentity.TileEntityEnchantmentTable; +import net.minecraft.tileentity.TileEntityEndGateway; +import net.minecraft.tileentity.TileEntityEnderChest; +import net.minecraft.tileentity.TileEntityFlowerPot; +import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraft.tileentity.TileEntityHopper; +import net.minecraft.tileentity.TileEntityMobSpawner; +import net.minecraft.tileentity.TileEntityNote; +import net.minecraft.tileentity.TileEntityShulkerBox; +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.tileentity.TileEntitySkull; +import net.minecraft.tileentity.TileEntityStructure; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.block.CraftBanner; +import org.bukkit.craftbukkit.block.CraftBeacon; +import org.bukkit.craftbukkit.block.CraftBlockEntityState; +import org.bukkit.craftbukkit.block.CraftBrewingStand; +import org.bukkit.craftbukkit.block.CraftChest; +import org.bukkit.craftbukkit.block.CraftCommandBlock; +import org.bukkit.craftbukkit.block.CraftComparator; +import org.bukkit.craftbukkit.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.block.CraftDaylightDetector; +import org.bukkit.craftbukkit.block.CraftDispenser; +import org.bukkit.craftbukkit.block.CraftDropper; +import org.bukkit.craftbukkit.block.CraftEnchantingTable; +import org.bukkit.craftbukkit.block.CraftEndGateway; +import org.bukkit.craftbukkit.block.CraftEnderChest; +import org.bukkit.craftbukkit.block.CraftFlowerPot; +import org.bukkit.craftbukkit.block.CraftFurnace; +import org.bukkit.craftbukkit.block.CraftHopper; +import org.bukkit.craftbukkit.block.CraftJukebox; +import org.bukkit.craftbukkit.block.CraftNoteBlock; +import org.bukkit.craftbukkit.block.CraftShulkerBox; +import org.bukkit.craftbukkit.block.CraftSign; +import org.bukkit.craftbukkit.block.CraftSkull; +import org.bukkit.craftbukkit.block.CraftStructureBlock; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.BlockStateMeta; + +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta { + + @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) + static final ItemMetaKey BLOCK_ENTITY_TAG = new ItemMetaKey("BlockEntityTag"); + + final Material material; + NBTTagCompound blockEntityTag; + + CraftMetaBlockState(CraftMetaItem meta, Material material) { + super(meta); + this.material = material; + + if (!(meta instanceof CraftMetaBlockState) + || ((CraftMetaBlockState) meta).material != material) { + blockEntityTag = null; + return; + } + + CraftMetaBlockState te = (CraftMetaBlockState) meta; + this.blockEntityTag = te.blockEntityTag; + } + + CraftMetaBlockState(NBTTagCompound tag, Material material) { + super(tag); + this.material = material; + + if (tag.hasKey(BLOCK_ENTITY_TAG.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { + blockEntityTag = tag.getCompoundTag(BLOCK_ENTITY_TAG.NBT); + } else { + blockEntityTag = null; + } + } + + CraftMetaBlockState(Map map) { + super(map); + String matName = SerializableMeta.getString(map, "blockMaterial", true); + Material m = Material.getMaterial(matName); + if (m != null) { + material = m; + } else { + material = Material.AIR; + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (blockEntityTag != null) { + tag.setTag(BLOCK_ENTITY_TAG.NBT, blockEntityTag); + } + } + + @Override + void deserializeInternal(NBTTagCompound tag) { + if (tag.hasKey(BLOCK_ENTITY_TAG.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { + blockEntityTag = tag.getCompoundTag(BLOCK_ENTITY_TAG.NBT); + } + } + + @Override + void serializeInternal(final Map internalTags) { + if (blockEntityTag != null) { + internalTags.put(BLOCK_ENTITY_TAG.NBT, blockEntityTag); + } + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + builder.put("blockMaterial", material.name()); + return builder; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (blockEntityTag != null) { + hash = 61 * hash + this.blockEntityTag.hashCode(); + } + return original != hash ? CraftMetaBlockState.class.hashCode() ^ hash : hash; + } + + @Override + public boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaBlockState) { + CraftMetaBlockState that = (CraftMetaBlockState) meta; + + return Objects.equal(this.blockEntityTag, that.blockEntityTag); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaBlockState || blockEntityTag == null); + } + + @Override + boolean isEmpty() { + return super.isEmpty() && blockEntityTag == null; + } + + @Override + boolean applicableTo(Material type) { + switch(type){ + case FURNACE: + case CHEST: + case TRAPPED_CHEST: + case JUKEBOX: + case DISPENSER: + case DROPPER: + case SIGN: + case MOB_SPAWNER: + case NOTE_BLOCK: + case BREWING_STAND_ITEM: + case ENCHANTMENT_TABLE: + case COMMAND: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + case BEACON: + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + case HOPPER: + case REDSTONE_COMPARATOR: + case FLOWER_POT_ITEM: + case SHIELD: + case STRUCTURE_BLOCK: + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + case ENDER_CHEST: + return true; + } + return false; + } + + @Override + public CraftMetaBlockState clone() { + CraftMetaBlockState meta = (CraftMetaBlockState) super.clone(); + if (blockEntityTag != null) { + meta.blockEntityTag = blockEntityTag.copy(); + } + return meta; + } + + @Override + public boolean hasBlockState() { + return blockEntityTag != null; + } + + @Override + public BlockState getBlockState() { + if (blockEntityTag != null) { + switch (material) { + case SHIELD: + blockEntityTag.setString("id", "banner"); + break; + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + blockEntityTag.setString("id", "shulker_box"); + break; + } + } + TileEntity te = (blockEntityTag == null) ? null : TileEntity.create(null, blockEntityTag); + + switch (material) { + case SIGN: + case SIGN_POST: + case WALL_SIGN: + if (te == null) { + te = new TileEntitySign(); + } + return new CraftSign(material, (TileEntitySign) te); + case CHEST: + case TRAPPED_CHEST: + if (te == null) { + te = new TileEntityChest(); + } + return new CraftChest(material, (TileEntityChest) te); + case BURNING_FURNACE: + case FURNACE: + if (te == null) { + te = new TileEntityFurnace(); + } + return new CraftFurnace(material, (TileEntityFurnace) te); + case DISPENSER: + if (te == null) { + te = new TileEntityDispenser(); + } + return new CraftDispenser(material, (TileEntityDispenser) te); + case DROPPER: + if (te == null) { + te = new TileEntityDropper(); + } + return new CraftDropper(material, (TileEntityDropper) te); + case END_GATEWAY: + if (te == null) { + te = new TileEntityEndGateway(); + } + return new CraftEndGateway(material, (TileEntityEndGateway) te); + case HOPPER: + if (te == null) { + te = new TileEntityHopper(); + } + return new CraftHopper(material, (TileEntityHopper) te); + case MOB_SPAWNER: + if (te == null) { + te = new TileEntityMobSpawner(); + } + return new CraftCreatureSpawner(material, (TileEntityMobSpawner) te); + case NOTE_BLOCK: + if (te == null) { + te = new TileEntityNote(); + } + return new CraftNoteBlock(material, (TileEntityNote) te); + case JUKEBOX: + if (te == null) { + te = new BlockJukebox.TileEntityJukebox(); + } + return new CraftJukebox(material, (BlockJukebox.TileEntityJukebox) te); + case BREWING_STAND_ITEM: + if (te == null) { + te = new TileEntityBrewingStand(); + } + return new CraftBrewingStand(material, (TileEntityBrewingStand) te); + case SKULL: + if (te == null) { + te = new TileEntitySkull(); + } + return new CraftSkull(material, (TileEntitySkull) te); + case COMMAND: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + if (te == null) { + te = new TileEntityCommandBlock(); + } + return new CraftCommandBlock(material, (TileEntityCommandBlock) te); + case BEACON: + if (te == null) { + te = new TileEntityBeacon(); + } + return new CraftBeacon(material, (TileEntityBeacon) te); + case SHIELD: + case BANNER: + case WALL_BANNER: + case STANDING_BANNER: + if (te == null) { + te = new TileEntityBanner(); + } + return new CraftBanner(material, (TileEntityBanner) te); + case FLOWER_POT_ITEM: + if (te == null) { + te = new TileEntityFlowerPot(); + } + return new CraftFlowerPot(material, (TileEntityFlowerPot) te); + case STRUCTURE_BLOCK: + if (te == null) { + te = new TileEntityStructure(); + } + return new CraftStructureBlock(material, (TileEntityStructure) te); + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + if (te == null) { + te = new TileEntityShulkerBox(); + } + return new CraftShulkerBox(material, (TileEntityShulkerBox) te); + case ENCHANTMENT_TABLE: + if (te == null) { + te = new TileEntityEnchantmentTable(); + } + return new CraftEnchantingTable(material, (TileEntityEnchantmentTable) te); + case ENDER_CHEST: + if (te == null){ + te = new TileEntityEnderChest(); + } + return new CraftEnderChest(material, (TileEntityEnderChest) te); + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + if (te == null){ + te = new TileEntityDaylightDetector(); + } + return new CraftDaylightDetector(material, (TileEntityDaylightDetector) te); + case REDSTONE_COMPARATOR: + if (te == null){ + te = new TileEntityComparator(); + } + return new CraftComparator(material, (TileEntityComparator) te); + case PISTON_BASE: + default: + throw new IllegalStateException("Missing blockState for " + material); + } + } + + @Override + public void setBlockState(BlockState blockState) { + Validate.notNull(blockState, "blockState must not be null"); + + boolean valid; + switch (material) { + case SIGN: + case SIGN_POST: + case WALL_SIGN: + valid = blockState instanceof CraftSign; + break; + case CHEST: + case TRAPPED_CHEST: + valid = blockState instanceof CraftChest; + break; + case BURNING_FURNACE: + case FURNACE: + valid = blockState instanceof CraftFurnace; + break; + case DISPENSER: + valid = blockState instanceof CraftDispenser; + break; + case DROPPER: + valid = blockState instanceof CraftDropper; + break; + case END_GATEWAY: + valid = blockState instanceof CraftEndGateway; + break; + case HOPPER: + valid = blockState instanceof CraftHopper; + break; + case MOB_SPAWNER: + valid = blockState instanceof CraftCreatureSpawner; + break; + case NOTE_BLOCK: + valid = blockState instanceof CraftNoteBlock; + break; + case JUKEBOX: + valid = blockState instanceof CraftJukebox; + break; + case BREWING_STAND_ITEM: + valid = blockState instanceof CraftBrewingStand; + break; + case SKULL: + valid = blockState instanceof CraftSkull; + break; + case COMMAND: + case COMMAND_REPEATING: + case COMMAND_CHAIN: + valid = blockState instanceof CraftCommandBlock; + break; + case BEACON: + valid = blockState instanceof CraftBeacon; + break; + case SHIELD: + case BANNER: + case WALL_BANNER: + case STANDING_BANNER: + valid = blockState instanceof CraftBanner; + break; + case FLOWER_POT_ITEM: + valid = blockState instanceof CraftFlowerPot; + break; + case STRUCTURE_BLOCK: + valid = blockState instanceof CraftStructureBlock; + break; + case WHITE_SHULKER_BOX: + case ORANGE_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case LIGHT_BLUE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + case LIME_SHULKER_BOX: + case PINK_SHULKER_BOX: + case GRAY_SHULKER_BOX: + case SILVER_SHULKER_BOX: + case CYAN_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BROWN_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case RED_SHULKER_BOX: + case BLACK_SHULKER_BOX: + valid = blockState instanceof CraftShulkerBox; + break; + case ENCHANTMENT_TABLE: + valid = blockState instanceof CraftEnchantingTable; + break; + case ENDER_CHEST: + valid = blockState instanceof CraftEnderChest; + break; + case DAYLIGHT_DETECTOR: + case DAYLIGHT_DETECTOR_INVERTED: + valid = blockState instanceof CraftDaylightDetector; + break; + case REDSTONE_COMPARATOR: + valid = blockState instanceof CraftComparator; + break; + default: + valid = false; + break; + } + + Validate.isTrue(valid, "Invalid blockState for " + material); + + blockEntityTag = ((CraftBlockEntityState) blockState).getSnapshotNBT(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java new file mode 100644 index 00000000..8e45cce9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java @@ -0,0 +1,432 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; + +import net.minecraft.util.text.ITextComponent; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.BookMeta; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap.Builder; +import java.util.AbstractList; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; + +@DelegateDeserialization(SerializableMeta.class) +public class CraftMetaBook extends CraftMetaItem implements BookMeta { + static final ItemMetaKey BOOK_TITLE = new ItemMetaKey("title"); + static final ItemMetaKey BOOK_AUTHOR = new ItemMetaKey("author"); + static final ItemMetaKey BOOK_PAGES = new ItemMetaKey("pages"); + static final ItemMetaKey RESOLVED = new ItemMetaKey("resolved"); + static final ItemMetaKey GENERATION = new ItemMetaKey("generation"); + static final int MAX_PAGES = 50; + static final int MAX_PAGE_LENGTH = 320; // 256 limit + 64 characters to allow for psuedo colour codes + static final int MAX_TITLE_LENGTH = 32; + + protected String title; + protected String author; + public List pages = new ArrayList(); + protected Integer generation; + + CraftMetaBook(CraftMetaItem meta) { + super(meta); + + if (meta instanceof CraftMetaBook) { + CraftMetaBook bookMeta = (CraftMetaBook) meta; + this.title = bookMeta.title; + this.author = bookMeta.author; + pages.addAll(bookMeta.pages); + this.generation = bookMeta.generation; + } + } + + CraftMetaBook(NBTTagCompound tag) { + this(tag, true); + } + + CraftMetaBook(NBTTagCompound tag, boolean handlePages) { + super(tag); + + if (tag.hasKey(BOOK_TITLE.NBT)) { + this.title = tag.getString(BOOK_TITLE.NBT); + } + + if (tag.hasKey(BOOK_AUTHOR.NBT)) { + this.author = tag.getString(BOOK_AUTHOR.NBT); + } + + boolean resolved = false; + if (tag.hasKey(RESOLVED.NBT)) { + resolved = tag.getBoolean(RESOLVED.NBT); + } + + if (tag.hasKey(GENERATION.NBT)) { + generation = tag.getInteger(GENERATION.NBT); + } + + if (tag.hasKey(BOOK_PAGES.NBT) && handlePages) { + NBTTagList pages = tag.getTagList(BOOK_PAGES.NBT, CraftMagicNumbers.NBT.TAG_STRING); + + for (int i = 0; i < Math.min(pages.tagCount(), MAX_PAGES); i++) { + String page = pages.getStringTagAt(i); + if (resolved) { + try { + this.pages.add(ITextComponent.Serializer.jsonToComponent(page)); + continue; + } catch (Exception e) { + // Ignore and treat as an old book + } + } + addPage(page); + } + } + } + + CraftMetaBook(Map map) { + super(map); + + setAuthor(SerializableMeta.getString(map, BOOK_AUTHOR.BUKKIT, true)); + + setTitle(SerializableMeta.getString(map, BOOK_TITLE.BUKKIT, true)); + + Iterable pages = SerializableMeta.getObject(Iterable.class, map, BOOK_PAGES.BUKKIT, true); + if(pages != null) { + for (Object page : pages) { + if (page instanceof String) { + addPage((String) page); + } + } + } + + generation = SerializableMeta.getObject(Integer.class, map, GENERATION.BUKKIT, true); + } + + @Override + void applyToItem(NBTTagCompound itemData) { + applyToItem(itemData, true); + } + + void applyToItem(NBTTagCompound itemData, boolean handlePages) { + super.applyToItem(itemData); + + if (hasTitle()) { + itemData.setString(BOOK_TITLE.NBT, this.title); + } + + if (hasAuthor()) { + itemData.setString(BOOK_AUTHOR.NBT, this.author); + } + + if (handlePages) { + if (hasPages()) { + NBTTagList list = new NBTTagList(); + for (ITextComponent page : pages) { + list.appendTag(new NBTTagString(CraftChatMessage.fromComponent(page))); + } + itemData.setTag(BOOK_PAGES.NBT, list); + } + + itemData.removeTag(RESOLVED.NBT); + } + + if (generation != null) { + itemData.setInteger(GENERATION.NBT, generation); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isBookEmpty(); + } + + boolean isBookEmpty() { + return !(hasPages() || hasAuthor() || hasTitle()); + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case WRITTEN_BOOK: + case BOOK_AND_QUILL: + return true; + default: + return false; + } + } + + public boolean hasAuthor() { + return !Strings.isNullOrEmpty(author); + } + + public boolean hasTitle() { + return !Strings.isNullOrEmpty(title); + } + + public boolean hasPages() { + return !pages.isEmpty(); + } + + public boolean hasGeneration() { + return generation != null; + } + + public String getTitle() { + return this.title; + } + + public boolean setTitle(final String title) { + if (title == null) { + this.title = null; + return true; + } else if (title.length() > MAX_TITLE_LENGTH) { + return false; + } + + this.title = title; + return true; + } + + public String getAuthor() { + return this.author; + } + + public void setAuthor(final String author) { + this.author = author; + } + + @Override + public Generation getGeneration() { + return (generation == null) ? null : Generation.values()[generation]; + } + + @Override + public void setGeneration(Generation generation) { + this.generation = (generation == null) ? null : generation.ordinal(); + } + + public String getPage(final int page) { + Validate.isTrue(isValidPage(page), "Invalid page number"); + return CraftChatMessage.fromComponent(pages.get(page - 1)); + } + + public void setPage(final int page, final String text) { + if (!isValidPage(page)) { + throw new IllegalArgumentException("Invalid page number " + page + "/" + pages.size()); + } + + String newText = text == null ? "" : text.length() > MAX_PAGE_LENGTH ? text.substring(0, MAX_PAGE_LENGTH) : text; + pages.set(page - 1, CraftChatMessage.fromString(newText, true)[0]); + } + + public void setPages(final String... pages) { + this.pages.clear(); + + addPage(pages); + } + + public void addPage(final String... pages) { + for (String page : pages) { + if (this.pages.size() >= MAX_PAGES) { + return; + } + + if (page == null) { + page = ""; + } else if (page.length() > MAX_PAGE_LENGTH) { + page = page.substring(0, MAX_PAGE_LENGTH); + } + + this.pages.add(CraftChatMessage.fromString(page, true)[0]); + } + } + + public int getPageCount() { + return pages.size(); + } + + public List getPages() { + final List copy = ImmutableList.copyOf(pages); + return new AbstractList() { + + @Override + public String get(int index) { + return CraftChatMessage.fromComponent(copy.get(index)); + } + + @Override + public int size() { + return copy.size(); + } + }; + } + + public void setPages(List pages) { + this.pages.clear(); + for (String page : pages) { + addPage(page); + } + } + + private boolean isValidPage(int page) { + return page > 0 && page <= pages.size(); + } + + @Override + public CraftMetaBook clone() { + CraftMetaBook meta = (CraftMetaBook) super.clone(); + meta.pages = new ArrayList(pages); + return meta; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasTitle()) { + hash = 61 * hash + this.title.hashCode(); + } + if (hasAuthor()) { + hash = 61 * hash + 13 * this.author.hashCode(); + } + if (hasPages()) { + hash = 61 * hash + 17 * this.pages.hashCode(); + } + if (hasGeneration()) { + hash = 61 * hash + 19 * this.generation.hashCode(); + } + return original != hash ? CraftMetaBook.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaBook) { + CraftMetaBook that = (CraftMetaBook) meta; + + return (hasTitle() ? that.hasTitle() && this.title.equals(that.title) : !that.hasTitle()) + && (hasAuthor() ? that.hasAuthor() && this.author.equals(that.author) : !that.hasAuthor()) + && (hasPages() ? that.hasPages() && this.pages.equals(that.pages) : !that.hasPages()) + && (hasGeneration() ? that.hasGeneration() && this.generation.equals(that.generation) : !that.hasGeneration()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaBook || isBookEmpty()); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasTitle()) { + builder.put(BOOK_TITLE.BUKKIT, title); + } + + if (hasAuthor()) { + builder.put(BOOK_AUTHOR.BUKKIT, author); + } + + if (hasPages()) { + List pagesString = new ArrayList(); + for (ITextComponent comp : pages) { + pagesString.add(CraftChatMessage.fromComponent(comp)); + } + builder.put(BOOK_PAGES.BUKKIT, pagesString); + } + + if (generation != null) { + builder.put(GENERATION.BUKKIT, generation); + } + + return builder; + } + + // Spigot start + private final BookMeta.Spigot spigot = new BookMeta.Spigot() { + + @Override + public BaseComponent[] getPage(final int page) { + Validate.isTrue(isValidPage(page), "Invalid page number"); + return ComponentSerializer.parse(ITextComponent.Serializer.componentToJson(pages.get(page - 1))); + } + + @Override + public void setPage(final int page, final BaseComponent... text) { + if (!isValidPage(page)) { + throw new IllegalArgumentException("Invalid page number " + page + "/" + pages.size()); + } + + BaseComponent[] newText = text == null ? new BaseComponent[0] : text; + CraftMetaBook.this.pages.set(page - 1, ITextComponent.Serializer.jsonToComponent(ComponentSerializer.toString(newText))); + } + + @Override + public void setPages(final BaseComponent[]... pages) { + CraftMetaBook.this.pages.clear(); + + addPage(pages); + } + + @Override + public void addPage(final BaseComponent[]... pages) { + for (BaseComponent[] page : pages) { + if (CraftMetaBook.this.pages.size() >= MAX_PAGES) { + return; + } + + if (page == null) { + page = new BaseComponent[0]; + } + + CraftMetaBook.this.pages.add(ITextComponent.Serializer.jsonToComponent(ComponentSerializer.toString(page))); + } + } + + @Override + public List getPages() { + final List copy = ImmutableList.copyOf(CraftMetaBook.this.pages); + return new AbstractList() { + + @Override + public BaseComponent[] get(int index) { + return ComponentSerializer.parse(ITextComponent.Serializer.componentToJson(copy.get(index))); + } + + @Override + public int size() { + return copy.size(); + } + }; + } + + @Override + public void setPages(List pages) { + CraftMetaBook.this.pages.clear(); + for (BaseComponent[] page : pages) { + addPage(page); + } + } + }; + + @Override + public BookMeta.Spigot spigot() { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java new file mode 100644 index 00000000..5c19760c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java @@ -0,0 +1,132 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; +import net.minecraft.util.text.ITextComponent; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.BookMeta; + +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaBookSigned extends CraftMetaBook implements BookMeta { + + CraftMetaBookSigned(CraftMetaItem meta) { + super(meta); + } + + CraftMetaBookSigned(NBTTagCompound tag) { + super(tag, false); + + boolean resolved = true; + if (tag.hasKey(RESOLVED.NBT)) { + resolved = tag.getBoolean(RESOLVED.NBT); + } + + if (tag.hasKey(BOOK_PAGES.NBT)) { + NBTTagList pages = tag.getTagList(BOOK_PAGES.NBT, CraftMagicNumbers.NBT.TAG_STRING); + + for (int i = 0; i < Math.min(pages.tagCount(), MAX_PAGES); i++) { + String page = pages.getStringTagAt(i); + if (resolved) { + try { + this.pages.add(ITextComponent.Serializer.jsonToComponent(page)); + continue; + } catch (Exception e) { + // Ignore and treat as an old book + } + } + addPage(page); + } + } + } + + CraftMetaBookSigned(Map map) { + super(map); + } + + @Override + void applyToItem(NBTTagCompound itemData) { + super.applyToItem(itemData, false); + + if (hasTitle()) { + itemData.setString(BOOK_TITLE.NBT, this.title); + } else { + itemData.setString(BOOK_TITLE.NBT, " "); + } + + if (hasAuthor()) { + itemData.setString(BOOK_AUTHOR.NBT, this.author); + } else { + itemData.setString(BOOK_AUTHOR.NBT, " "); + } + + if (hasPages()) { + NBTTagList list = new NBTTagList(); + for (ITextComponent page : pages) { + list.appendTag(new NBTTagString( + ITextComponent.Serializer.componentToJson(page) + )); + } + itemData.setTag(BOOK_PAGES.NBT, list); + } + itemData.setBoolean(RESOLVED.NBT, true); + + if (generation != null) { + itemData.setInteger(GENERATION.NBT, generation); + } else { + itemData.setInteger(GENERATION.NBT, 0); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty(); + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case WRITTEN_BOOK: + case BOOK_AND_QUILL: + return true; + default: + return false; + } + } + + @Override + public CraftMetaBookSigned clone() { + CraftMetaBookSigned meta = (CraftMetaBookSigned) super.clone(); + return meta; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + return original != hash ? CraftMetaBookSigned.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + return super.equalsCommon(meta); + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaBookSigned || isBookEmpty()); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java new file mode 100644 index 00000000..3cb4f18a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java @@ -0,0 +1,130 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import org.bukkit.FireworkEffect; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.FireworkEffectMeta; + +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaCharge extends CraftMetaItem implements FireworkEffectMeta { + static final ItemMetaKey EXPLOSION = new ItemMetaKey("Explosion", "firework-effect"); + + private FireworkEffect effect; + + CraftMetaCharge(CraftMetaItem meta) { + super(meta); + + if (meta instanceof CraftMetaCharge) { + effect = ((CraftMetaCharge) meta).effect; + } + } + + CraftMetaCharge(Map map) { + super(map); + + setEffect(SerializableMeta.getObject(FireworkEffect.class, map, EXPLOSION.BUKKIT, true)); + } + + CraftMetaCharge(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(EXPLOSION.NBT)) { + effect = CraftMetaFirework.getEffect(tag.getCompoundTag(EXPLOSION.NBT)); + } + } + + @Override + public void setEffect(FireworkEffect effect) { + this.effect = effect; + } + + @Override + public boolean hasEffect() { + return effect != null; + } + + @Override + public FireworkEffect getEffect() { + return effect; + } + + @Override + void applyToItem(NBTTagCompound itemTag) { + super.applyToItem(itemTag); + + if (hasEffect()) { + itemTag.setTag(EXPLOSION.NBT, CraftMetaFirework.getExplosion(effect)); + } + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case FIREWORK_CHARGE: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && !hasChargeMeta(); + } + + boolean hasChargeMeta() { + return hasEffect(); + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaCharge) { + CraftMetaCharge that = (CraftMetaCharge) meta; + + return (hasEffect() ? that.hasEffect() && this.effect.equals(that.effect) : !that.hasEffect()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaCharge || !hasChargeMeta()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + + if (hasEffect()) { + hash = 61 * hash + effect.hashCode(); + } + + return hash != original ? CraftMetaCharge.class.hashCode() ^ hash : hash; + } + + @Override + public CraftMetaCharge clone() { + return (CraftMetaCharge) super.clone(); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasEffect()) { + builder.put(EXPLOSION.BUKKIT, effect); + } + + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java new file mode 100644 index 00000000..7add7be6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java @@ -0,0 +1,167 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaEnchantedBook extends CraftMetaItem implements EnchantmentStorageMeta { + static final ItemMetaKey STORED_ENCHANTMENTS = new ItemMetaKey("StoredEnchantments", "stored-enchants"); + + private Map enchantments; + + CraftMetaEnchantedBook(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaEnchantedBook)) { + return; + } + + CraftMetaEnchantedBook that = (CraftMetaEnchantedBook) meta; + + if (that.hasEnchants()) { + this.enchantments = new HashMap(that.enchantments); + } + } + + CraftMetaEnchantedBook(NBTTagCompound tag) { + super(tag); + + if (!tag.hasKey(STORED_ENCHANTMENTS.NBT)) { + return; + } + + enchantments = buildEnchantments(tag, STORED_ENCHANTMENTS); + } + + CraftMetaEnchantedBook(Map map) { + super(map); + + enchantments = buildEnchantments(map, STORED_ENCHANTMENTS); + } + + @Override + void applyToItem(NBTTagCompound itemTag) { + super.applyToItem(itemTag); + + applyEnchantments(enchantments, itemTag, STORED_ENCHANTMENTS); + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case ENCHANTED_BOOK: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isEnchantedEmpty(); + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaEnchantedBook) { + CraftMetaEnchantedBook that = (CraftMetaEnchantedBook) meta; + + return (hasStoredEnchants() ? that.hasStoredEnchants() && this.enchantments.equals(that.enchantments) : !that.hasStoredEnchants()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaEnchantedBook || isEnchantedEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + + if (hasStoredEnchants()) { + hash = 61 * hash + enchantments.hashCode(); + } + + return original != hash ? CraftMetaEnchantedBook.class.hashCode() ^ hash : hash; + } + + @Override + public CraftMetaEnchantedBook clone() { + CraftMetaEnchantedBook meta = (CraftMetaEnchantedBook) super.clone(); + + if (this.enchantments != null) { + meta.enchantments = new HashMap(this.enchantments); + } + + return meta; + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + serializeEnchantments(enchantments, builder, STORED_ENCHANTMENTS); + + return builder; + } + + boolean isEnchantedEmpty() { + return !hasStoredEnchants(); + } + + public boolean hasStoredEnchant(Enchantment ench) { + return hasStoredEnchants() && enchantments.containsKey(ench); + } + + public int getStoredEnchantLevel(Enchantment ench) { + Integer level = hasStoredEnchants() ? enchantments.get(ench) : null; + if (level == null) { + return 0; + } + return level; + } + + public Map getStoredEnchants() { + return hasStoredEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.of(); + } + + public boolean addStoredEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + if (enchantments == null) { + enchantments = new HashMap(4); + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { + Integer old = enchantments.put(ench, level); + return old == null || old != level; + } + return false; + } + + public boolean removeStoredEnchant(Enchantment ench) { + return hasStoredEnchants() && enchantments.remove(ench) != null; + } + + public boolean hasStoredEnchants() { + return !(enchantments == null || enchantments.isEmpty()); + } + + public boolean hasConflictingStoredEnchant(Enchantment ench) { + return checkConflictingEnchants(enchantments, ench); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java new file mode 100644 index 00000000..8434c91b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java @@ -0,0 +1,394 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.FireworkEffect.Type; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific.To; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.FireworkMeta; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + /* + "Fireworks", "Explosion", "Explosions", "Flight", "Type", "Trail", "Flicker", "Colors", "FadeColors"; + + Fireworks + - Compound: Fireworks + -- Byte: Flight + -- List: Explosions + --- Compound: Explosion + ---- IntArray: Colors + ---- Byte: Type + ---- Boolean: Trail + ---- Boolean: Flicker + ---- IntArray: FadeColors + */ + + @Specific(To.NBT) + static final ItemMetaKey FIREWORKS = new ItemMetaKey("Fireworks"); + static final ItemMetaKey FLIGHT = new ItemMetaKey("Flight", "power"); + static final ItemMetaKey EXPLOSIONS = new ItemMetaKey("Explosions", "firework-effects"); + @Specific(To.NBT) + static final ItemMetaKey EXPLOSION_COLORS = new ItemMetaKey("Colors"); + @Specific(To.NBT) + static final ItemMetaKey EXPLOSION_TYPE = new ItemMetaKey("Type"); + @Specific(To.NBT) + static final ItemMetaKey EXPLOSION_TRAIL = new ItemMetaKey("Trail"); + @Specific(To.NBT) + static final ItemMetaKey EXPLOSION_FLICKER = new ItemMetaKey("Flicker"); + @Specific(To.NBT) + static final ItemMetaKey EXPLOSION_FADE = new ItemMetaKey("FadeColors"); + + private List effects; + private int power; + + CraftMetaFirework(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaFirework)) { + return; + } + + CraftMetaFirework that = (CraftMetaFirework) meta; + + this.power = that.power; + + if (that.hasEffects()) { + this.effects = new ArrayList(that.effects); + } + } + + CraftMetaFirework(NBTTagCompound tag) { + super(tag); + + if (!tag.hasKey(FIREWORKS.NBT)) { + return; + } + + NBTTagCompound fireworks = tag.getCompoundTag(FIREWORKS.NBT); + + power = 0xff & fireworks.getByte(FLIGHT.NBT); + + if (!fireworks.hasKey(EXPLOSIONS.NBT)) { + return; + } + + NBTTagList fireworkEffects = fireworks.getTagList(EXPLOSIONS.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + List effects = this.effects = new ArrayList(fireworkEffects.tagCount()); + + for (int i = 0; i < fireworkEffects.tagCount(); i++) { + effects.add(getEffect((NBTTagCompound) fireworkEffects.get(i))); + } + } + + static FireworkEffect getEffect(NBTTagCompound explosion) { + FireworkEffect.Builder effect = FireworkEffect.builder() + .flicker(explosion.getBoolean(EXPLOSION_FLICKER.NBT)) + .trail(explosion.getBoolean(EXPLOSION_TRAIL.NBT)) + .with(getEffectType(0xff & explosion.getByte(EXPLOSION_TYPE.NBT))); + + int[] colors = explosion.getIntArray(EXPLOSION_COLORS.NBT); + // People using buggy command generators specify a list rather than an int here, so recover with dummy data. + // Wrong: Colors: [1234] + // Right: Colors: [I;1234] + if (colors.length == 0) { + effect.withColor(Color.WHITE); + } + + for (int color : colors) { + effect.withColor(Color.fromRGB(color)); + } + + for (int color : explosion.getIntArray(EXPLOSION_FADE.NBT)) { + effect.withFade(Color.fromRGB(color)); + } + + return effect.build(); + } + + static NBTTagCompound getExplosion(FireworkEffect effect) { + NBTTagCompound explosion = new NBTTagCompound(); + + if (effect.hasFlicker()) { + explosion.setBoolean(EXPLOSION_FLICKER.NBT, true); + } + + if (effect.hasTrail()) { + explosion.setBoolean(EXPLOSION_TRAIL.NBT, true); + } + + addColors(explosion, EXPLOSION_COLORS, effect.getColors()); + addColors(explosion, EXPLOSION_FADE, effect.getFadeColors()); + + explosion.setByte(EXPLOSION_TYPE.NBT, (byte) getNBT(effect.getType())); + + return explosion; + } + + static int getNBT(Type type) { + switch (type) { + case BALL: + return 0; + case BALL_LARGE: + return 1; + case STAR: + return 2; + case CREEPER: + return 3; + case BURST: + return 4; + default: + throw new IllegalArgumentException("Unknown effect type " + type); + } + } + + static Type getEffectType(int nbt) { + switch (nbt) { + case 0: + return Type.BALL; + case 1: + return Type.BALL_LARGE; + case 2: + return Type.STAR; + case 3: + return Type.CREEPER; + case 4: + return Type.BURST; + default: + throw new IllegalArgumentException("Unknown effect type " + nbt); + } + } + + CraftMetaFirework(Map map) { + super(map); + + Integer power = SerializableMeta.getObject(Integer.class, map, FLIGHT.BUKKIT, true); + if (power != null) { + setPower(power); + } + + Iterable effects = SerializableMeta.getObject(Iterable.class, map, EXPLOSIONS.BUKKIT, true); + safelyAddEffects(effects); + } + + public boolean hasEffects() { + return !(effects == null || effects.isEmpty()); + } + + void safelyAddEffects(Iterable collection) { + if (collection == null || (collection instanceof Collection && ((Collection) collection).isEmpty())) { + return; + } + + List effects = this.effects; + if (effects == null) { + effects = this.effects = new ArrayList(); + } + + for (Object obj : collection) { + if (obj instanceof FireworkEffect) { + effects.add((FireworkEffect) obj); + } else { + throw new IllegalArgumentException(obj + " in " + collection + " is not a FireworkEffect"); + } + } + } + + @Override + void applyToItem(NBTTagCompound itemTag) { + super.applyToItem(itemTag); + if (isFireworkEmpty()) { + return; + } + + NBTTagCompound fireworks = itemTag.getCompoundTag(FIREWORKS.NBT); + itemTag.setTag(FIREWORKS.NBT, fireworks); + + if (hasEffects()) { + NBTTagList effects = new NBTTagList(); + for (FireworkEffect effect : this.effects) { + effects.appendTag(getExplosion(effect)); + } + + if (effects.tagCount() > 0) { + fireworks.setTag(EXPLOSIONS.NBT, effects); + } + } + + if (hasPower()) { + fireworks.setByte(FLIGHT.NBT, (byte) power); + } + } + + static void addColors(NBTTagCompound compound, ItemMetaKey key, List colors) { + if (colors.isEmpty()) { + return; + } + + final int[] colorArray = new int[colors.size()]; + int i = 0; + for (Color color : colors) { + colorArray[i++] = color.asRGB(); + } + + compound.setIntArray(key.NBT, colorArray); + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case FIREWORK: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isFireworkEmpty(); + } + + boolean isFireworkEmpty() { + return !(hasEffects() || hasPower()); + } + + boolean hasPower() { + return power != 0; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + + if (meta instanceof CraftMetaFirework) { + CraftMetaFirework that = (CraftMetaFirework) meta; + + return (hasPower() ? that.hasPower() && this.power == that.power : !that.hasPower()) + && (hasEffects() ? that.hasEffects() && this.effects.equals(that.effects) : !that.hasEffects()); + } + + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaFirework || isFireworkEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasPower()) { + hash = 61 * hash + power; + } + if (hasEffects()) { + hash = 61 * hash + 13 * effects.hashCode(); + } + return hash != original ? CraftMetaFirework.class.hashCode() ^ hash : hash; + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasEffects()) { + builder.put(EXPLOSIONS.BUKKIT, ImmutableList.copyOf(effects)); + } + + if (hasPower()) { + builder.put(FLIGHT.BUKKIT, power); + } + + return builder; + } + + @Override + public CraftMetaFirework clone() { + CraftMetaFirework meta = (CraftMetaFirework) super.clone(); + + if (this.effects != null) { + meta.effects = new ArrayList(this.effects); + } + + return meta; + } + + public void addEffect(FireworkEffect effect) { + Validate.notNull(effect, "Effect cannot be null"); + if (this.effects == null) { + this.effects = new ArrayList(); + } + this.effects.add(effect); + } + + public void addEffects(FireworkEffect...effects) { + Validate.notNull(effects, "Effects cannot be null"); + if (effects.length == 0) { + return; + } + + List list = this.effects; + if (list == null) { + list = this.effects = new ArrayList(); + } + + for (FireworkEffect effect : effects) { + Validate.notNull(effect, "Effect cannot be null"); + list.add(effect); + } + } + + public void addEffects(Iterable effects) { + Validate.notNull(effects, "Effects cannot be null"); + safelyAddEffects(effects); + } + + public List getEffects() { + return this.effects == null ? ImmutableList.of() : ImmutableList.copyOf(this.effects); + } + + public int getEffectsSize() { + return this.effects == null ? 0 : this.effects.size(); + } + + public void removeEffect(int index) { + if (this.effects == null) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); + } else { + this.effects.remove(index); + } + } + + public void clearEffects() { + this.effects = null; + } + + public int getPower() { + return this.power; + } + + public void setPower(int power) { + Validate.isTrue(power >= 0, "Power cannot be less than zero: ", power); + Validate.isTrue(power < 0x80, "Power cannot be more than 127: ", power); + this.power = power; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java new file mode 100644 index 00000000..709b0d20 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -0,0 +1,931 @@ +package org.bukkit.craftbukkit.inventory; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.craftbukkit.Overridden; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.Repairable; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.codec.binary.Base64; + +/** + * Children must include the following: + * + *

  • Constructor(CraftMetaItem meta) + *
  • Constructor(NBTTagCompound tag) + *
  • Constructor(Map map) + *

    + *
  • void applyToItem(NBTTagCompound tag) + *
  • boolean applicableTo(Material type) + *

    + *
  • boolean equalsCommon(CraftMetaItem meta) + *
  • boolean notUncommon(CraftMetaItem meta) + *

    + *
  • boolean isEmpty() + *
  • boolean is{Type}Empty() + *

    + *
  • int applyHash() + *
  • public Class clone() + *

    + *
  • Builder serialize(Builder builder) + *
  • SerializableMeta.Deserializers deserializer() + */ +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +class CraftMetaItem implements ItemMeta, Repairable { + + static class ItemMetaKey { + + @Retention(RetentionPolicy.SOURCE) + @Target(ElementType.FIELD) + @interface Specific { + enum To { + BUKKIT, + NBT, + ; + } + To value(); + } + + final String BUKKIT; + final String NBT; + + ItemMetaKey(final String both) { + this(both, both); + } + + ItemMetaKey(final String nbt, final String bukkit) { + this.NBT = nbt; + this.BUKKIT = bukkit; + } + } + + @SerializableAs("ItemMeta") + public static class SerializableMeta implements ConfigurationSerializable { + static final String TYPE_FIELD = "meta-type"; + + static final ImmutableMap, String> classMap; + static final ImmutableMap> constructorMap; + + static { + classMap = ImmutableMap., String>builder() + .put(CraftMetaBanner.class, "BANNER") + .put(CraftMetaBlockState.class, "TILE_ENTITY") + .put(CraftMetaBook.class, "BOOK") + .put(CraftMetaBookSigned.class, "BOOK_SIGNED") + .put(CraftMetaSkull.class, "SKULL") + .put(CraftMetaLeatherArmor.class, "LEATHER_ARMOR") + .put(CraftMetaMap.class, "MAP") + .put(CraftMetaPotion.class, "POTION") + .put(CraftMetaSpawnEgg.class, "SPAWN_EGG") + .put(CraftMetaEnchantedBook.class, "ENCHANTED") + .put(CraftMetaFirework.class, "FIREWORK") + .put(CraftMetaCharge.class, "FIREWORK_EFFECT") + .put(CraftMetaKnowledgeBook.class, "KNOWLEDGE_BOOK") + .put(CraftMetaItem.class, "UNSPECIFIC") + .build(); + + final ImmutableMap.Builder> classConstructorBuilder = ImmutableMap.builder(); + for (Map.Entry, String> mapping : classMap.entrySet()) { + try { + classConstructorBuilder.put(mapping.getValue(), mapping.getKey().getDeclaredConstructor(Map.class)); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + constructorMap = classConstructorBuilder.build(); + } + + private SerializableMeta() { + } + + public static ItemMeta deserialize(Map map) throws Throwable { + Validate.notNull(map, "Cannot deserialize null map"); + + String type = getString(map, TYPE_FIELD, false); + Constructor constructor = constructorMap.get(type); + + if (constructor == null) { + throw new IllegalArgumentException(type + " is not a valid " + TYPE_FIELD); + } + + try { + return constructor.newInstance(map); + } catch (final InstantiationException e) { + throw new AssertionError(e); + } catch (final IllegalAccessException e) { + throw new AssertionError(e); + } catch (final InvocationTargetException e) { + throw e.getCause(); + } + } + + public Map serialize() { + throw new AssertionError(); + } + + static String getString(Map map, Object field, boolean nullable) { + return getObject(String.class, map, field, nullable); + } + + static boolean getBoolean(Map map, Object field) { + Boolean value = getObject(Boolean.class, map, field, true); + return value != null && value; + } + + static T getObject(Class clazz, Map map, Object field, boolean nullable) { + final Object object = map.get(field); + + if (clazz.isInstance(object)) { + return clazz.cast(object); + } + if (object == null) { + if (!nullable) { + throw new NoSuchElementException(map + " does not contain " + field); + } + return null; + } + throw new IllegalArgumentException(field + "(" + object + ") is not a valid " + clazz); + } + } + + static final ItemMetaKey NAME = new ItemMetaKey("Name", "display-name"); + static final ItemMetaKey LOCNAME = new ItemMetaKey("LocName", "loc-name"); + @Specific(Specific.To.NBT) + static final ItemMetaKey DISPLAY = new ItemMetaKey("display"); + static final ItemMetaKey LORE = new ItemMetaKey("Lore", "lore"); + static final ItemMetaKey ENCHANTMENTS = new ItemMetaKey("ench", "enchants"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ENCHANTMENTS_ID = new ItemMetaKey("id"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); + static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES = new ItemMetaKey("AttributeModifiers"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_IDENTIFIER = new ItemMetaKey("AttributeName"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_NAME = new ItemMetaKey("Name"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_VALUE = new ItemMetaKey("Amount"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_TYPE = new ItemMetaKey("Operation"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_UUID_HIGH = new ItemMetaKey("UUIDMost"); + @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_UUID_LOW = new ItemMetaKey("UUIDLeast"); + @Specific(Specific.To.NBT) + static final ItemMetaKey HIDEFLAGS = new ItemMetaKey("HideFlags", "ItemFlags"); + @Specific(Specific.To.NBT) + static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable"); + + private String displayName; + private String locName; + private List lore; + private Map enchantments; + private int repairCost; + private int hideFlag; + private boolean unbreakable; + + private static final Set HANDLED_TAGS = Sets.newHashSet(); + + private NBTTagCompound internalTag; + private final Map unhandledTags = new HashMap(); + + CraftMetaItem(CraftMetaItem meta) { + if (meta == null) { + return; + } + + this.displayName = meta.displayName; + this.locName = meta.locName; + + if (meta.hasLore()) { + this.lore = new ArrayList(meta.lore); + } + + if (meta.hasEnchants()) { + this.enchantments = new HashMap(meta.enchantments); + } + + this.repairCost = meta.repairCost; + this.hideFlag = meta.hideFlag; + this.unbreakable = meta.unbreakable; + this.unhandledTags.putAll(meta.unhandledTags); + + this.internalTag = meta.internalTag; + if (this.internalTag != null) { + deserializeInternal(internalTag); + } + } + + CraftMetaItem(NBTTagCompound tag) { + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompoundTag(DISPLAY.NBT); + + if (display.hasKey(NAME.NBT)) { + displayName = display.getString(NAME.NBT); + } + + if (display.hasKey(LOCNAME.NBT)) { + locName = display.getString(LOCNAME.NBT); + } + + if (display.hasKey(LORE.NBT)) { + NBTTagList list = display.getTagList(LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); + lore = new ArrayList(list.tagCount()); + + for (int index = 0; index < list.tagCount(); index++) { + String line = list.getStringTagAt(index); + lore.add(line); + } + } + } + + this.enchantments = buildEnchantments(tag, ENCHANTMENTS); + + if (tag.hasKey(REPAIR.NBT)) { + repairCost = tag.getInteger(REPAIR.NBT); + } + + if (tag.hasKey(HIDEFLAGS.NBT)) { + hideFlag = tag.getInteger(HIDEFLAGS.NBT); + } + if (tag.hasKey(UNBREAKABLE.NBT)) { + unbreakable = tag.getBoolean(UNBREAKABLE.NBT); + } + + if (tag.getTag(ATTRIBUTES.NBT) instanceof NBTTagList) { + NBTTagList save = null; + NBTTagList nbttaglist = tag.getTagList(ATTRIBUTES.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + + for (int i = 0; i < nbttaglist.tagCount(); ++i) { + if (!(nbttaglist.get(i) instanceof NBTTagCompound)) { + continue; + } + NBTTagCompound nbttagcompound = (NBTTagCompound) nbttaglist.get(i); + + if (!nbttagcompound.hasKey(ATTRIBUTES_UUID_HIGH.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { + continue; + } + if (!nbttagcompound.hasKey(ATTRIBUTES_UUID_LOW.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { + continue; + } + if (!(nbttagcompound.getTag(ATTRIBUTES_IDENTIFIER.NBT) instanceof NBTTagString) || !CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES.contains(nbttagcompound.getString(ATTRIBUTES_IDENTIFIER.NBT))) { + continue; + } + if (!(nbttagcompound.getTag(ATTRIBUTES_NAME.NBT) instanceof NBTTagString) || nbttagcompound.getString(ATTRIBUTES_NAME.NBT).isEmpty()) { + continue; + } + if (!nbttagcompound.hasKey(ATTRIBUTES_VALUE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { + continue; + } + if (!nbttagcompound.hasKey(ATTRIBUTES_TYPE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER) || nbttagcompound.getInteger(ATTRIBUTES_TYPE.NBT) < 0 || nbttagcompound.getInteger(ATTRIBUTES_TYPE.NBT) > 2) { + continue; + } + + if (save == null) { + save = new NBTTagList(); + } + + NBTTagCompound entry = new NBTTagCompound(); + entry.setTag(ATTRIBUTES_UUID_HIGH.NBT, nbttagcompound.getTag(ATTRIBUTES_UUID_HIGH.NBT)); + entry.setTag(ATTRIBUTES_UUID_LOW.NBT, nbttagcompound.getTag(ATTRIBUTES_UUID_LOW.NBT)); + entry.setTag(ATTRIBUTES_IDENTIFIER.NBT, nbttagcompound.getTag(ATTRIBUTES_IDENTIFIER.NBT)); + entry.setTag(ATTRIBUTES_NAME.NBT, nbttagcompound.getTag(ATTRIBUTES_NAME.NBT)); + entry.setTag(ATTRIBUTES_VALUE.NBT, nbttagcompound.getTag(ATTRIBUTES_VALUE.NBT)); + entry.setTag(ATTRIBUTES_TYPE.NBT, nbttagcompound.getTag(ATTRIBUTES_TYPE.NBT)); + save.appendTag(entry); + } + + unhandledTags.put(ATTRIBUTES.NBT, save); + } + + Set keys = tag.getKeySet(); + for (String key : keys) { + if (!getHandledTags().contains(key)) { + unhandledTags.put(key, tag.getTag(key)); + } + } + } + + static Map buildEnchantments(NBTTagCompound tag, ItemMetaKey key) { + if (!tag.hasKey(key.NBT)) { + return null; + } + + NBTTagList ench = tag.getTagList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + Map enchantments = new HashMap(ench.tagCount()); + + for (int i = 0; i < ench.tagCount(); i++) { + int id = 0xffff & ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_ID.NBT); + int level = 0xffff & ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_LVL.NBT); + + Enchantment enchant = Enchantment.getById(id); + if (enchant != null) { + enchantments.put(enchant, level); + } + } + + return enchantments; + } + + CraftMetaItem(Map map) { + setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true)); + setLocalizedName(SerializableMeta.getString(map, LOCNAME.BUKKIT, true)); + + Iterable lore = SerializableMeta.getObject(Iterable.class, map, LORE.BUKKIT, true); + if (lore != null) { + safelyAdd(lore, this.lore = new ArrayList(), Integer.MAX_VALUE); + } + + enchantments = buildEnchantments(map, ENCHANTMENTS); + + Integer repairCost = SerializableMeta.getObject(Integer.class, map, REPAIR.BUKKIT, true); + if (repairCost != null) { + setRepairCost(repairCost); + } + + Iterable hideFlags = SerializableMeta.getObject(Iterable.class, map, HIDEFLAGS.BUKKIT, true); + if (hideFlags != null) { + for (Object hideFlagObject : hideFlags) { + String hideFlagString = (String) hideFlagObject; + try { + ItemFlag hideFlatEnum = ItemFlag.valueOf(hideFlagString); + addItemFlags(hideFlatEnum); + } catch (IllegalArgumentException ex) { + // Ignore when we got a old String which does not map to a Enum value anymore + } + } + } + + Boolean unbreakable = SerializableMeta.getObject(Boolean.class, map, UNBREAKABLE.BUKKIT, true); + if (unbreakable != null) { + setUnbreakable(unbreakable); + } + + String internal = SerializableMeta.getString(map, "internal", true); + if (internal != null) { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(internal)); + try { + internalTag = CompressedStreamTools.readCompressed(buf); + deserializeInternal(internalTag); + Set keys = internalTag.getKeySet(); + for (String key : keys) { + if (!getHandledTags().contains(key)) { + unhandledTags.put(key, internalTag.getTag(key)); + } + } + } catch (IOException ex) { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + void deserializeInternal(NBTTagCompound tag) { + } + + static Map buildEnchantments(Map map, ItemMetaKey key) { + Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); + if (ench == null) { + return null; + } + + Map enchantments = new HashMap(ench.size()); + for (Map.Entry entry : ench.entrySet()) { + // Doctor older enchants + String enchantKey = entry.getKey().toString(); + if (enchantKey.equals("SWEEPING")) { + enchantKey = "SWEEPING_EDGE"; + } + + Enchantment enchantment = Enchantment.getByName(enchantKey); + if ((enchantment != null) && (entry.getValue() instanceof Integer)) { + enchantments.put(enchantment, (Integer) entry.getValue()); + } + } + + return enchantments; + } + + @Overridden + void applyToItem(NBTTagCompound itemTag) { + if (hasDisplayName()) { + setDisplayTag(itemTag, NAME.NBT, new NBTTagString(displayName)); + } + if (hasLocalizedName()){ + setDisplayTag(itemTag, LOCNAME.NBT, new NBTTagString(locName)); + } + + if (hasLore()) { + setDisplayTag(itemTag, LORE.NBT, createStringList(lore)); + } + + if (hideFlag != 0) { + itemTag.setInteger(HIDEFLAGS.NBT, hideFlag); + } + + applyEnchantments(enchantments, itemTag, ENCHANTMENTS); + + if (hasRepairCost()) { + itemTag.setInteger(REPAIR.NBT, repairCost); + } + + if (isUnbreakable()) { + itemTag.setBoolean(UNBREAKABLE.NBT, unbreakable); + } + + for (Map.Entry e : unhandledTags.entrySet()) { + itemTag.setTag(e.getKey(), e.getValue()); + } + } + + static NBTTagList createStringList(List list) { + if (list == null || list.isEmpty()) { + return null; + } + + NBTTagList tagList = new NBTTagList(); + for (String value : list) { + tagList.appendTag(new NBTTagString(value)); + } + + return tagList; + } + + static void applyEnchantments(Map enchantments, NBTTagCompound tag, ItemMetaKey key) { + if (enchantments == null || enchantments.size() == 0) { + return; + } + + NBTTagList list = new NBTTagList(); + + for (Map.Entry entry : enchantments.entrySet()) { + NBTTagCompound subtag = new NBTTagCompound(); + + subtag.setShort(ENCHANTMENTS_ID.NBT, (short) entry.getKey().getId()); + subtag.setShort(ENCHANTMENTS_LVL.NBT, entry.getValue().shortValue()); + + list.appendTag(subtag); + } + + tag.setTag(key.NBT, list); + } + + void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { + final NBTTagCompound display = tag.getCompoundTag(DISPLAY.NBT); + + if (!tag.hasKey(DISPLAY.NBT)) { + tag.setTag(DISPLAY.NBT, display); + } + + display.setTag(key, value); + } + + @Overridden + boolean applicableTo(Material type) { + return type != Material.AIR; + } + + @Overridden + boolean isEmpty() { + return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable()); + } + + public String getDisplayName() { + return displayName; + } + + public final void setDisplayName(String name) { + this.displayName = name; + } + + public boolean hasDisplayName() { + return !Strings.isNullOrEmpty(displayName); + } + + @Override + public String getLocalizedName() { + return locName; + } + + @Override + public void setLocalizedName(String name) { + this.locName = name; + } + + @Override + public boolean hasLocalizedName() { + return !Strings.isNullOrEmpty(locName); + } + + public boolean hasLore() { + return this.lore != null && !this.lore.isEmpty(); + } + + public boolean hasRepairCost() { + return repairCost > 0; + } + + public boolean hasEnchant(Enchantment ench) { + Validate.notNull(ench, "Enchantment cannot be null"); + return hasEnchants() && enchantments.containsKey(ench); + } + + public int getEnchantLevel(Enchantment ench) { + Validate.notNull(ench, "Enchantment cannot be null"); + Integer level = hasEnchants() ? enchantments.get(ench) : null; + if (level == null) { + return 0; + } + return level; + } + + public Map getEnchants() { + return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.of(); + } + + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + Validate.notNull(ench, "Enchantment cannot be null"); + if (enchantments == null) { + enchantments = new HashMap(4); + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { + Integer old = enchantments.put(ench, level); + return old == null || old != level; + } + return false; + } + + public boolean removeEnchant(Enchantment ench) { + Validate.notNull(ench, "Enchantment cannot be null"); + return hasEnchants() && enchantments.remove(ench) != null; + } + + public boolean hasEnchants() { + return !(enchantments == null || enchantments.isEmpty()); + } + + public boolean hasConflictingEnchant(Enchantment ench) { + return checkConflictingEnchants(enchantments, ench); + } + + @Override + public void addItemFlags(ItemFlag... hideFlags) { + for (ItemFlag f : hideFlags) { + this.hideFlag |= getBitModifier(f); + } + } + + @Override + public void removeItemFlags(ItemFlag... hideFlags) { + for (ItemFlag f : hideFlags) { + this.hideFlag &= ~getBitModifier(f); + } + } + + @Override + public Set getItemFlags() { + Set currentFlags = EnumSet.noneOf(ItemFlag.class); + + for (ItemFlag f : ItemFlag.values()) { + if (hasItemFlag(f)) { + currentFlags.add(f); + } + } + + return currentFlags; + } + + @Override + public boolean hasItemFlag(ItemFlag flag) { + int bitModifier = getBitModifier(flag); + return (this.hideFlag & bitModifier) == bitModifier; + } + + private byte getBitModifier(ItemFlag hideFlag) { + return (byte) (1 << hideFlag.ordinal()); + } + + public List getLore() { + return this.lore == null ? null : new ArrayList(this.lore); + } + + public void setLore(List lore) { // too tired to think if .clone is better + if (lore == null) { + this.lore = null; + } else { + if (this.lore == null) { + safelyAdd(lore, this.lore = new ArrayList(lore.size()), Integer.MAX_VALUE); + } else { + this.lore.clear(); + safelyAdd(lore, this.lore, Integer.MAX_VALUE); + } + } + } + + public int getRepairCost() { + return repairCost; + } + + public void setRepairCost(int cost) { // TODO: Does this have limits? + repairCost = cost; + } + + @Override + public boolean isUnbreakable() { + return unbreakable; + } + + @Override + public void setUnbreakable(boolean unbreakable) { + this.unbreakable = unbreakable; + } + + @Override + public final boolean equals(Object object) { + if (object == null) { + return false; + } + if (object == this) { + return true; + } + if (!(object instanceof CraftMetaItem)) { + return false; + } + return CraftItemFactory.instance().equals(this, (ItemMeta) object); + } + + /** + * This method is almost as weird as notUncommon. + * Only return false if your common internals are unequal. + * Checking your own internals is redundant if you are not common, as notUncommon is meant for checking those 'not common' variables. + */ + @Overridden + boolean equalsCommon(CraftMetaItem that) { + return ((this.hasDisplayName() ? that.hasDisplayName() && this.displayName.equals(that.displayName) : !that.hasDisplayName())) + && (this.hasLocalizedName()? that.hasLocalizedName()&& this.locName.equals(that.locName) : !that.hasLocalizedName()) + && (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants()) + && (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore()) + && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) + && (this.unhandledTags.equals(that.unhandledTags)) + && (this.hideFlag == that.hideFlag) + && (this.isUnbreakable() == that.isUnbreakable()); + } + + /** + * This method is a bit weird... + * Return true if you are a common class OR your uncommon parts are empty. + * Empty uncommon parts implies the NBT data would be equivalent if both were applied to an item + */ + @Overridden + boolean notUncommon(CraftMetaItem meta) { + return true; + } + + @Override + public final int hashCode() { + return applyHash(); + } + + @Overridden + int applyHash() { + int hash = 3; + hash = 61 * hash + (hasDisplayName() ? this.displayName.hashCode() : 0); + hash = 61 * hash + (hasLocalizedName()? this.locName.hashCode() : 0); + hash = 61 * hash + (hasLore() ? this.lore.hashCode() : 0); + hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); + hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); + hash = 61 * hash + unhandledTags.hashCode(); + hash = 61 * hash + hideFlag; + hash = 61 * hash + (isUnbreakable() ? 1231 : 1237); + return hash; + } + + @Overridden + @Override + public CraftMetaItem clone() { + try { + CraftMetaItem clone = (CraftMetaItem) super.clone(); + if (this.lore != null) { + clone.lore = new ArrayList(this.lore); + } + if (this.enchantments != null) { + clone.enchantments = new HashMap(this.enchantments); + } + clone.hideFlag = this.hideFlag; + clone.unbreakable = this.unbreakable; + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + public final Map serialize() { + ImmutableMap.Builder map = ImmutableMap.builder(); + map.put(SerializableMeta.TYPE_FIELD, SerializableMeta.classMap.get(getClass())); + serialize(map); + return map.build(); + } + + @Overridden + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + if (hasDisplayName()) { + builder.put(NAME.BUKKIT, displayName); + } + if (hasLocalizedName()) { + builder.put(LOCNAME.BUKKIT, locName); + } + + if (hasLore()) { + builder.put(LORE.BUKKIT, ImmutableList.copyOf(lore)); + } + + serializeEnchantments(enchantments, builder, ENCHANTMENTS); + + if (hasRepairCost()) { + builder.put(REPAIR.BUKKIT, repairCost); + } + + List hideFlags = new ArrayList(); + for (ItemFlag hideFlagEnum : getItemFlags()) { + hideFlags.add(hideFlagEnum.name()); + } + if (!hideFlags.isEmpty()) { + builder.put(HIDEFLAGS.BUKKIT, hideFlags); + } + + if (isUnbreakable()) { + builder.put(UNBREAKABLE.BUKKIT, unbreakable); + } + + final Map internalTags = new HashMap(unhandledTags); + serializeInternal(internalTags); + if (!internalTags.isEmpty()) { + NBTTagCompound internal = new NBTTagCompound(); + for (Map.Entry e : internalTags.entrySet()) { + internal.setTag(e.getKey(), e.getValue()); + } + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + CompressedStreamTools.writeCompressed(internal, buf); + builder.put("internal", Base64.encodeBase64String(buf.toByteArray())); + } catch (IOException ex) { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } + } + + return builder; + } + + void serializeInternal(final Map unhandledTags) { + } + + static void serializeEnchantments(Map enchantments, ImmutableMap.Builder builder, ItemMetaKey key) { + if (enchantments == null || enchantments.isEmpty()) { + return; + } + + ImmutableMap.Builder enchants = ImmutableMap.builder(); + for (Map.Entry enchant : enchantments.entrySet()) { + enchants.put(enchant.getKey().getName(), enchant.getValue()); + } + + builder.put(key.BUKKIT, enchants.build()); + } + + static void safelyAdd(Iterable addFrom, Collection addTo, int maxItemLength) { + if (addFrom == null) { + return; + } + + for (Object object : addFrom) { + if (!(object instanceof String)) { + if (object != null) { + throw new IllegalArgumentException(addFrom + " cannot contain non-string " + object.getClass().getName()); + } + + addTo.add(""); + } else { + String page = object.toString(); + + if (page.length() > maxItemLength) { + page = page.substring(0, maxItemLength); + } + + addTo.add(page); + } + } + } + + static boolean checkConflictingEnchants(Map enchantments, Enchantment ench) { + if (enchantments == null || enchantments.isEmpty()) { + return false; + } + + for (Enchantment enchant : enchantments.keySet()) { + if (enchant.conflictsWith(ench)) { + return true; + } + } + + return false; + } + + @Override + public final String toString() { + return SerializableMeta.classMap.get(getClass()) + "_META:" + serialize(); // TODO: cry + } + + public static Set getHandledTags() { + synchronized (HANDLED_TAGS) { + if (HANDLED_TAGS.isEmpty()) { + HANDLED_TAGS.addAll(Arrays.asList( + DISPLAY.NBT, + REPAIR.NBT, + ENCHANTMENTS.NBT, + HIDEFLAGS.NBT, + UNBREAKABLE.NBT, + CraftMetaMap.MAP_SCALING.NBT, + CraftMetaPotion.POTION_EFFECTS.NBT, + CraftMetaPotion.DEFAULT_POTION.NBT, + CraftMetaSkull.SKULL_OWNER.NBT, + CraftMetaSkull.SKULL_PROFILE.NBT, + CraftMetaSpawnEgg.ENTITY_TAG.NBT, + CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, + CraftMetaBook.BOOK_TITLE.NBT, + CraftMetaBook.BOOK_AUTHOR.NBT, + CraftMetaBook.BOOK_PAGES.NBT, + CraftMetaBook.RESOLVED.NBT, + CraftMetaBook.GENERATION.NBT, + CraftMetaFirework.FIREWORKS.NBT, + CraftMetaEnchantedBook.STORED_ENCHANTMENTS.NBT, + CraftMetaCharge.EXPLOSION.NBT, + CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, + CraftMetaKnowledgeBook.BOOK_RECIPES.NBT + )); + } + return HANDLED_TAGS; + } + } + + // Spigot start + private final Spigot spigot = new Spigot() + { + @Override + public void setUnbreakable(boolean setUnbreakable) + { + CraftMetaItem.this.setUnbreakable(setUnbreakable); + } + + @Override + public boolean isUnbreakable() + { + return CraftMetaItem.this.unbreakable; + } + }; + + @Override + public Spigot spigot() + { + return spigot; + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java new file mode 100644 index 00000000..06465bfc --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java @@ -0,0 +1,173 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.collect.ImmutableMap.Builder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.inventory.meta.KnowledgeBookMeta; + +@DelegateDeserialization(SerializableMeta.class) +public class CraftMetaKnowledgeBook extends CraftMetaItem implements KnowledgeBookMeta { + + static final ItemMetaKey BOOK_RECIPES = new ItemMetaKey("Recipes"); + static final int MAX_RECIPES = Short.MAX_VALUE; + + protected List recipes = new ArrayList(); + + CraftMetaKnowledgeBook(CraftMetaItem meta) { + super(meta); + + if (meta instanceof CraftMetaKnowledgeBook) { + CraftMetaKnowledgeBook bookMeta = (CraftMetaKnowledgeBook) meta; + this.recipes.addAll(bookMeta.recipes); + } + } + + CraftMetaKnowledgeBook(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(BOOK_RECIPES.NBT)) { + NBTTagList pages = tag.getTagList(BOOK_RECIPES.NBT, 8); + + for (int i = 0; i < pages.tagCount(); i++) { + String recipe = pages.getStringTagAt(i); + + addRecipe(CraftNamespacedKey.fromString(recipe)); + } + } + } + + CraftMetaKnowledgeBook(Map map) { + super(map); + + Iterable pages = SerializableMeta.getObject(Iterable.class, map, BOOK_RECIPES.BUKKIT, true); + if (pages != null) { + for (Object page : pages) { + if (page instanceof String) { + addRecipe(CraftNamespacedKey.fromString((String) page)); + } + } + } + } + + void applyToItem(NBTTagCompound itemData) { + super.applyToItem(itemData); + + if (hasRecipes()) { + NBTTagList list = new NBTTagList(); + for (NamespacedKey recipe : this.recipes) { + list.appendTag(new NBTTagString(recipe.toString())); + } + itemData.setTag(BOOK_RECIPES.NBT, list); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isBookEmpty(); + } + + boolean isBookEmpty() { + return !(hasRecipes()); + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case KNOWLEDGE_BOOK: + return true; + default: + return false; + } + } + + @Override + public boolean hasRecipes() { + return !recipes.isEmpty(); + } + + @Override + public void addRecipe(NamespacedKey... recipes) { + for (NamespacedKey recipe : recipes) { + if (recipe != null) { + if (this.recipes.size() >= MAX_RECIPES) { + return; + } + + this.recipes.add(recipe); + } + } + } + + @Override + public List getRecipes() { + return Collections.unmodifiableList(recipes); + } + + @Override + public void setRecipes(List recipes) { + this.recipes.clear(); + for (NamespacedKey recipe : this.recipes) { + addRecipe(recipe); + } + } + + @Override + public CraftMetaKnowledgeBook clone() { + CraftMetaKnowledgeBook meta = (CraftMetaKnowledgeBook) super.clone(); + meta.recipes = new ArrayList(recipes); + return meta; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasRecipes()) { + hash = 61 * hash + 17 * this.recipes.hashCode(); + } + return original != hash ? CraftMetaKnowledgeBook.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaKnowledgeBook) { + CraftMetaKnowledgeBook that = (CraftMetaKnowledgeBook) meta; + + return (hasRecipes() ? that.hasRecipes() && this.recipes.equals(that.recipes) : !that.hasRecipes()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaKnowledgeBook || isBookEmpty()); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasRecipes()) { + List recipesString = new ArrayList(); + for (NamespacedKey recipe : recipes) { + recipesString.add(recipe.toString()); + } + builder.put(BOOK_RECIPES.BUKKIT, recipesString); + } + + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java new file mode 100644 index 00000000..fa0d25a9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java @@ -0,0 +1,134 @@ +package org.bukkit.craftbukkit.inventory; + +import static org.bukkit.craftbukkit.inventory.CraftItemFactory.DEFAULT_LEATHER_COLOR; + +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagInt; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; + +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + static final ItemMetaKey COLOR = new ItemMetaKey("color"); + + private Color color = DEFAULT_LEATHER_COLOR; + + CraftMetaLeatherArmor(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaLeatherArmor)) { + return; + } + + CraftMetaLeatherArmor armorMeta = (CraftMetaLeatherArmor) meta; + this.color = armorMeta.color; + } + + CraftMetaLeatherArmor(NBTTagCompound tag) { + super(tag); + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompoundTag(DISPLAY.NBT); + if (display.hasKey(COLOR.NBT)) { + color = Color.fromRGB(display.getInteger(COLOR.NBT)); + } + } + } + + CraftMetaLeatherArmor(Map map) { + super(map); + setColor(SerializableMeta.getObject(Color.class, map, COLOR.BUKKIT, true)); + } + + @Override + void applyToItem(NBTTagCompound itemTag) { + super.applyToItem(itemTag); + + if (hasColor()) { + setDisplayTag(itemTag, COLOR.NBT, new NBTTagInt(color.asRGB())); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isLeatherArmorEmpty(); + } + + boolean isLeatherArmorEmpty() { + return !(hasColor()); + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return true; + default: + return false; + } + } + + @Override + public CraftMetaLeatherArmor clone() { + return (CraftMetaLeatherArmor) super.clone(); + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color == null ? DEFAULT_LEATHER_COLOR : color; + } + + boolean hasColor() { + return !DEFAULT_LEATHER_COLOR.equals(color); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasColor()) { + builder.put(COLOR.BUKKIT, color); + } + + return builder; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaLeatherArmor) { + CraftMetaLeatherArmor that = (CraftMetaLeatherArmor) meta; + + return color.equals(that.color); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaLeatherArmor || isLeatherArmorEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasColor()) { + hash ^= color.hashCode(); + } + return original != hash ? CraftMetaSkull.class.hashCode() ^ hash : hash; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java new file mode 100644 index 00000000..9129ceb5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java @@ -0,0 +1,220 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagString; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.MapMeta; + +import com.google.common.collect.ImmutableMap; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaMap extends CraftMetaItem implements MapMeta { + static final ItemMetaKey MAP_SCALING = new ItemMetaKey("map_is_scaling", "scaling"); + static final ItemMetaKey MAP_LOC_NAME = new ItemMetaKey("LocName", "display-loc-name"); + static final ItemMetaKey MAP_COLOR = new ItemMetaKey("MapColor", "display-map-color"); + static final byte SCALING_EMPTY = (byte) 0; + static final byte SCALING_TRUE = (byte) 1; + static final byte SCALING_FALSE = (byte) 2; + + private byte scaling = SCALING_EMPTY; + private String locName; + private Color color; + + CraftMetaMap(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaMap)) { + return; + } + + CraftMetaMap map = (CraftMetaMap) meta; + this.scaling = map.scaling; + this.locName = map.locName; + this.color = map.color; + } + + CraftMetaMap(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(MAP_SCALING.NBT)) { + this.scaling = tag.getBoolean(MAP_SCALING.NBT) ? SCALING_TRUE : SCALING_FALSE; + } + + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompoundTag(DISPLAY.NBT); + + if (display.hasKey(MAP_LOC_NAME.NBT)) { + locName = display.getString(MAP_LOC_NAME.NBT); + } + + if (display.hasKey(MAP_COLOR.NBT)) { + color = Color.fromRGB(display.getInteger(MAP_COLOR.NBT)); + } + } + } + + CraftMetaMap(Map map) { + super(map); + + Boolean scaling = SerializableMeta.getObject(Boolean.class, map, MAP_SCALING.BUKKIT, true); + if (scaling != null) { + setScaling(scaling); + } + + String locName = SerializableMeta.getString(map, MAP_LOC_NAME.BUKKIT, true); + if (locName != null) { + setLocationName(locName); + } + + Color color = SerializableMeta.getObject(Color.class, map, MAP_COLOR.BUKKIT, true); + if (color != null) { + setColor(color); + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (hasScaling()) { + tag.setBoolean(MAP_SCALING.NBT, isScaling()); + } + + if (hasLocationName()) { + setDisplayTag(tag, MAP_LOC_NAME.NBT, new NBTTagString(getLocationName())); + } + + if (hasColor()) { + setDisplayTag(tag, MAP_COLOR.NBT, new NBTTagInt(color.asRGB())); + } + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case MAP: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isMapEmpty(); + } + + boolean isMapEmpty() { + return !(hasScaling() | hasLocationName() || hasColor()); + } + + boolean hasScaling() { + return scaling != SCALING_EMPTY; + } + + public boolean isScaling() { + return scaling == SCALING_TRUE; + } + + public void setScaling(boolean scaling) { + this.scaling = scaling ? SCALING_TRUE : SCALING_FALSE; + } + + @Override + public boolean hasLocationName() { + return this.locName != null; + } + + @Override + public String getLocationName() { + return this.locName; + } + + @Override + public void setLocationName(String name) { + this.locName = name; + } + + @Override + public boolean hasColor() { + return this.color != null; + } + + @Override + public Color getColor() { + return this.color; + } + + @Override + public void setColor(Color color) { + this.color = color; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaMap) { + CraftMetaMap that = (CraftMetaMap) meta; + + return (this.scaling == that.scaling) + && (hasLocationName() ? that.hasLocationName() && this.locName.equals(that.locName) : !that.hasLocationName()) + && (hasColor() ? that.hasColor() && this.color.equals(that.color) : !that.hasColor()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaMap || isMapEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + + if (hasScaling()) { + hash ^= 0x22222222 << (isScaling() ? 1 : -1); + } + if (hasLocationName()) { + hash = 61 * hash + locName.hashCode(); + } + if (hasColor()) { + hash = 61 * hash + color.hashCode(); + } + + return original != hash ? CraftMetaMap.class.hashCode() ^ hash : hash; + } + + + public CraftMetaMap clone() { + return (CraftMetaMap) super.clone(); + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + + if (hasScaling()) { + builder.put(MAP_SCALING.BUKKIT, isScaling()); + } + + if (hasLocationName()) { + builder.put(MAP_LOC_NAME.BUKKIT, getLocationName()); + } + + if (hasColor()) { + builder.put(MAP_COLOR.BUKKIT, getColor()); + } + + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java new file mode 100644 index 00000000..e94eb7f4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java @@ -0,0 +1,334 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + static final ItemMetaKey AMPLIFIER = new ItemMetaKey("Amplifier", "amplifier"); + static final ItemMetaKey AMBIENT = new ItemMetaKey("Ambient", "ambient"); + static final ItemMetaKey DURATION = new ItemMetaKey("Duration", "duration"); + static final ItemMetaKey SHOW_PARTICLES = new ItemMetaKey("ShowParticles", "has-particles"); + static final ItemMetaKey POTION_EFFECTS = new ItemMetaKey("CustomPotionEffects", "custom-effects"); + static final ItemMetaKey POTION_COLOR = new ItemMetaKey("CustomPotionColor", "custom-color"); + static final ItemMetaKey ID = new ItemMetaKey("Id", "potion-id"); + static final ItemMetaKey DEFAULT_POTION = new ItemMetaKey("Potion", "potion-type"); + + // Having an initial "state" in ItemMeta seems bit dirty but the UNCRAFTABLE potion type + // is treated as the empty form of the meta because it represents an empty potion with no effect + private PotionData type = new PotionData(PotionType.UNCRAFTABLE, false, false); + private List customEffects; + private Color color; + + CraftMetaPotion(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaPotion)) { + return; + } + CraftMetaPotion potionMeta = (CraftMetaPotion) meta; + this.type = potionMeta.type; + this.color = potionMeta.color; + if (potionMeta.hasCustomEffects()) { + this.customEffects = new ArrayList(potionMeta.customEffects); + } + } + + CraftMetaPotion(NBTTagCompound tag) { + super(tag); + if (tag.hasKey(DEFAULT_POTION.NBT)) { + type = CraftPotionUtil.toBukkit(tag.getString(DEFAULT_POTION.NBT)); + } + if (tag.hasKey(POTION_COLOR.NBT)) { + color = Color.fromRGB(tag.getInteger(POTION_COLOR.NBT)); + } + if (tag.hasKey(POTION_EFFECTS.NBT)) { + NBTTagList list = tag.getTagList(POTION_EFFECTS.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + int length = list.tagCount(); + customEffects = new ArrayList(length); + + for (int i = 0; i < length; i++) { + NBTTagCompound effect = list.getCompoundTagAt(i); + PotionEffectType type = PotionEffectType.getById(effect.getByte(ID.NBT)); + int amp = effect.getByte(AMPLIFIER.NBT); + int duration = effect.getInteger(DURATION.NBT); + boolean ambient = effect.getBoolean(AMBIENT.NBT); + boolean particles = effect.getBoolean(SHOW_PARTICLES.NBT); + customEffects.add(new PotionEffect(type, duration, amp, ambient, particles)); + } + } + } + + CraftMetaPotion(Map map) { + super(map); + type = CraftPotionUtil.toBukkit(SerializableMeta.getString(map, DEFAULT_POTION.BUKKIT, true)); + + Color color = SerializableMeta.getObject(Color.class, map, POTION_COLOR.BUKKIT, true); + if (color != null) { + setColor(color); + } + + Iterable rawEffectList = SerializableMeta.getObject(Iterable.class, map, POTION_EFFECTS.BUKKIT, true); + if (rawEffectList == null) { + return; + } + + for (Object obj : rawEffectList) { + if (!(obj instanceof PotionEffect)) { + throw new IllegalArgumentException("Object in effect list is not valid. " + obj.getClass()); + } + addCustomEffect((PotionEffect) obj, true); + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + tag.setString(DEFAULT_POTION.NBT, CraftPotionUtil.fromBukkit(type)); + + if (hasColor()) { + tag.setInteger(POTION_COLOR.NBT, color.asRGB()); + } + + if (customEffects != null) { + NBTTagList effectList = new NBTTagList(); + tag.setTag(POTION_EFFECTS.NBT, effectList); + + for (PotionEffect effect : customEffects) { + NBTTagCompound effectData = new NBTTagCompound(); + effectData.setByte(ID.NBT, (byte) effect.getType().getId()); + effectData.setByte(AMPLIFIER.NBT, (byte) effect.getAmplifier()); + effectData.setInteger(DURATION.NBT, effect.getDuration()); + effectData.setBoolean(AMBIENT.NBT, effect.isAmbient()); + effectData.setBoolean(SHOW_PARTICLES.NBT, effect.hasParticles()); + effectList.appendTag(effectData); + } + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isPotionEmpty(); + } + + boolean isPotionEmpty() { + return (type.getType() == PotionType.UNCRAFTABLE) && !(hasCustomEffects() || hasColor()); + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case POTION: + case SPLASH_POTION: + case LINGERING_POTION: + case TIPPED_ARROW: + return true; + default: + return false; + } + } + + @Override + public CraftMetaPotion clone() { + CraftMetaPotion clone = (CraftMetaPotion) super.clone(); + clone.type = type; + if (this.customEffects != null) { + clone.customEffects = new ArrayList(this.customEffects); + } + return clone; + } + + @Override + public void setBasePotionData(PotionData data) { + Validate.notNull(data, "PotionData cannot be null"); + this.type = data; + } + + @Override + public PotionData getBasePotionData() { + return type; + } + + public boolean hasCustomEffects() { + return customEffects != null; + } + + public List getCustomEffects() { + if (hasCustomEffects()) { + return ImmutableList.copyOf(customEffects); + } + return ImmutableList.of(); + } + + public boolean addCustomEffect(PotionEffect effect, boolean overwrite) { + Validate.notNull(effect, "Potion effect must not be null"); + + int index = indexOfEffect(effect.getType()); + if (index != -1) { + if (overwrite) { + PotionEffect old = customEffects.get(index); + if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) { + return false; + } + customEffects.set(index, effect); + return true; + } else { + return false; + } + } else { + if (customEffects == null) { + customEffects = new ArrayList(); + } + customEffects.add(effect); + return true; + } + } + + public boolean removeCustomEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + + if (!hasCustomEffects()) { + return false; + } + + boolean changed = false; + Iterator iterator = customEffects.iterator(); + while (iterator.hasNext()) { + PotionEffect effect = iterator.next(); + if (type.equals(effect.getType())) { + iterator.remove(); + changed = true; + } + } + if (customEffects.isEmpty()) { + customEffects = null; + } + return changed; + } + + public boolean hasCustomEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + return indexOfEffect(type) != -1; + } + + public boolean setMainEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + int index = indexOfEffect(type); + if (index == -1 || index == 0) { + return false; + } + + PotionEffect old = customEffects.get(0); + customEffects.set(0, customEffects.get(index)); + customEffects.set(index, old); + return true; + } + + private int indexOfEffect(PotionEffectType type) { + if (!hasCustomEffects()) { + return -1; + } + + for (int i = 0; i < customEffects.size(); i++) { + if (customEffects.get(i).getType().equals(type)) { + return i; + } + } + return -1; + } + + public boolean clearCustomEffects() { + boolean changed = hasCustomEffects(); + customEffects = null; + return changed; + } + + @Override + public boolean hasColor() { + return color != null; + } + + @Override + public Color getColor() { + return color; + } + + @Override + public void setColor(Color color) { + this.color = color; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (type.getType() != PotionType.UNCRAFTABLE) { + hash = 73 * hash + type.hashCode(); + } + if (hasColor()) { + hash = 73 * hash + color.hashCode(); + } + if (hasCustomEffects()) { + hash = 73 * hash + customEffects.hashCode(); + } + return original != hash ? CraftMetaPotion.class.hashCode() ^ hash : hash; + } + + @Override + public boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaPotion) { + CraftMetaPotion that = (CraftMetaPotion) meta; + + return type.equals(that.type) + && (this.hasCustomEffects() ? that.hasCustomEffects() && this.customEffects.equals(that.customEffects) : !that.hasCustomEffects()) + && (this.hasColor() ? that.hasColor() && this.color.equals(that.color) : !that.hasColor()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaPotion || isPotionEmpty()); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + if (type.getType() != PotionType.UNCRAFTABLE) { + builder.put(DEFAULT_POTION.BUKKIT, CraftPotionUtil.fromBukkit(type)); + } + + if (hasColor()) { + builder.put(POTION_COLOR.BUKKIT, getColor()); + } + + if (hasCustomEffects()) { + builder.put(POTION_EFFECTS.BUKKIT, ImmutableList.copyOf(this.customEffects)); + } + + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java new file mode 100644 index 00000000..2685952b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java @@ -0,0 +1,203 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntitySkull; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.meta.SkullMeta; + +import com.google.common.collect.ImmutableMap.Builder; +import com.mojang.authlib.GameProfile; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + + @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) + static final ItemMetaKey SKULL_PROFILE = new ItemMetaKey("SkullProfile"); + + static final ItemMetaKey SKULL_OWNER = new ItemMetaKey("SkullOwner", "skull-owner"); + static final int MAX_OWNER_LENGTH = 16; + + private GameProfile profile; + + CraftMetaSkull(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaSkull)) { + return; + } + CraftMetaSkull skullMeta = (CraftMetaSkull) meta; + this.profile = skullMeta.profile; + } + + CraftMetaSkull(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(SKULL_OWNER.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { + profile = NBTUtil.readGameProfileFromNBT(tag.getCompoundTag(SKULL_OWNER.NBT)); + } else if (tag.hasKey(SKULL_OWNER.NBT, CraftMagicNumbers.NBT.TAG_STRING) && !tag.getString(SKULL_OWNER.NBT).isEmpty()) { + profile = new GameProfile(null, tag.getString(SKULL_OWNER.NBT)); + } + } + + CraftMetaSkull(Map map) { + super(map); + if (profile == null) { + setOwner(SerializableMeta.getString(map, SKULL_OWNER.BUKKIT, true)); + } + } + + @Override + void deserializeInternal(NBTTagCompound tag) { + if (tag.hasKey(SKULL_PROFILE.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { + profile = NBTUtil.readGameProfileFromNBT(tag.getCompoundTag(SKULL_PROFILE.NBT)); + } + } + + @Override + void serializeInternal(final Map internalTags) { + if (profile != null) { + NBTTagCompound nbtData = new NBTTagCompound(); + NBTUtil.writeGameProfile(nbtData, profile); + internalTags.put(SKULL_PROFILE.NBT, nbtData); + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (profile != null) { + // Fill in textures + profile = TileEntitySkull.updateGameprofile(profile); + + NBTTagCompound owner = new NBTTagCompound(); + NBTUtil.writeGameProfile(owner, profile); + tag.setTag(SKULL_OWNER.NBT, owner); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isSkullEmpty(); + } + + boolean isSkullEmpty() { + return profile == null; + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case SKULL_ITEM: + return true; + default: + return false; + } + } + + @Override + public CraftMetaSkull clone() { + return (CraftMetaSkull) super.clone(); + } + + public boolean hasOwner() { + return profile != null && profile.getName() != null; + } + + public String getOwner() { + return hasOwner() ? profile.getName() : null; + } + + @Override + public OfflinePlayer getOwningPlayer() { + if (hasOwner()) { + if (profile.getId() != null) { + return Bukkit.getOfflinePlayer(profile.getId()); + } + + if (profile.getName() != null) { + return Bukkit.getOfflinePlayer(profile.getName()); + } + } + + return null; + } + + public boolean setOwner(String name) { + if (name != null && name.length() > MAX_OWNER_LENGTH) { + return false; + } + + if (name == null) { + profile = null; + } else { + // Paper start - Use Online Players Skull + GameProfile newProfile = null; + EntityPlayerMP player = MinecraftServer.getServerCB().getPlayerList().getPlayerByUsername(name); + if (player != null) { + newProfile = player.getGameProfile(); + } + if (newProfile == null) { + newProfile = new GameProfile(null, name); + } + profile = newProfile; + // Paper end + } + + return true; + } + + @Override + public boolean setOwningPlayer(OfflinePlayer owner) { + profile = (owner == null) ? null : new GameProfile(owner.getUniqueId(), owner.getName()); + + return true; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasOwner()) { + hash = 61 * hash + profile.hashCode(); + } + return original != hash ? CraftMetaSkull.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaSkull) { + CraftMetaSkull that = (CraftMetaSkull) meta; + + return (this.hasOwner() ? that.hasOwner() && this.profile.equals(that.profile) : !that.hasOwner()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaSkull || isSkullEmpty()); + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + if (hasOwner()) { + return builder.put(SKULL_OWNER.BUKKIT, this.profile.getName()); + } + return builder; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java new file mode 100644 index 00000000..5bcbf79f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java @@ -0,0 +1,186 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap.Builder; +import java.util.Map; + +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.datafix.FixTypes; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.meta.SpawnEggMeta; + +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { + + static final ItemMetaKey ENTITY_TAG = new ItemMetaKey("EntityTag", "entity-tag"); + @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) + static final ItemMetaKey ENTITY_ID = new ItemMetaKey("id"); + + private EntityType spawnedType; + private NBTTagCompound entityTag; + + CraftMetaSpawnEgg(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaSpawnEgg)) { + return; + } + + CraftMetaSpawnEgg egg = (CraftMetaSpawnEgg) meta; + this.spawnedType = egg.spawnedType; + } + + CraftMetaSpawnEgg(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(ENTITY_TAG.NBT)) { + entityTag = tag.getCompoundTag(ENTITY_TAG.NBT); + + if (entityTag.hasKey(ENTITY_ID.NBT)) { + this.spawnedType = EntityType.fromName(new ResourceLocation(entityTag.getString(ENTITY_ID.NBT)).getResourcePath()); + } + } + } + + CraftMetaSpawnEgg(Map map) { + super(map); + + String entityType = SerializableMeta.getString(map, ENTITY_ID.BUKKIT, true); + setSpawnedType(EntityType.fromName(entityType)); + } + + @Override + void deserializeInternal(NBTTagCompound tag) { + super.deserializeInternal(tag); + + if (tag.hasKey(ENTITY_TAG.NBT)) { + entityTag = tag.getCompoundTag(ENTITY_TAG.NBT); + MinecraftServer.getServerCB().getDataFixer().process(FixTypes.ENTITY, entityTag); // PAIL: convert TODO: identify DataConverterTypes after implementation + + if (entityTag.hasKey(ENTITY_ID.NBT)) { + this.spawnedType = EntityType.fromName(new ResourceLocation(entityTag.getString(ENTITY_ID.NBT)).getResourcePath()); + } + } + } + + @Override + void serializeInternal(Map internalTags) { + if (entityTag != null) { + internalTags.put(ENTITY_TAG.NBT, entityTag); + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (!isSpawnEggEmpty() && entityTag == null) { + entityTag = new NBTTagCompound(); + } + + if (hasSpawnedType()) { + entityTag.setString(ENTITY_ID.NBT, new ResourceLocation(spawnedType.getName()).toString()); + } + + if (entityTag != null) { + tag.setTag(ENTITY_TAG.NBT, entityTag); + } + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case MONSTER_EGG: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isSpawnEggEmpty(); + } + + boolean isSpawnEggEmpty() { + return !(hasSpawnedType() || entityTag != null); + } + + boolean hasSpawnedType() { + return spawnedType != null; + } + + @Override + public EntityType getSpawnedType() { + return spawnedType; + } + + @Override + public void setSpawnedType(EntityType type) { + Preconditions.checkArgument(type == null || type.getName() != null, "Spawn egg type must have name (%s)", type); + + this.spawnedType = type; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaSpawnEgg) { + CraftMetaSpawnEgg that = (CraftMetaSpawnEgg) meta; + + return hasSpawnedType() ? that.hasSpawnedType() && this.spawnedType.equals(that.spawnedType) : !that.hasSpawnedType() + && entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : entityTag == null; + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaSpawnEgg || isSpawnEggEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + + if (hasSpawnedType()) { + hash = 73 * hash + spawnedType.hashCode(); + } + if (entityTag != null) { + hash = 73 * hash + entityTag.hashCode(); + } + + return original != hash ? CraftMetaSpawnEgg.class.hashCode() ^ hash : hash; + } + + @Override + Builder serialize(Builder builder) { + super.serialize(builder); + + if (hasSpawnedType()) { + builder.put(ENTITY_ID.BUKKIT, spawnedType.getName()); + } + + return builder; + } + + @Override + public CraftMetaSpawnEgg clone() { + CraftMetaSpawnEgg clone = (CraftMetaSpawnEgg) super.clone(); + + clone.spawnedType = spawnedType; + if (entityTag != null) { + clone.entityTag = entityTag.copy(); + } + + return clone; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java new file mode 100644 index 00000000..d3e03e24 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java @@ -0,0 +1,7 @@ +package org.bukkit.craftbukkit.inventory; + +import org.bukkit.inventory.Recipe; + +public interface CraftRecipe extends Recipe { + void addToCraftingManager(); +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java new file mode 100644 index 00000000..0fa1c490 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java @@ -0,0 +1,60 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapedRecipes; +import net.minecraft.util.NonNullList; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ShapedRecipe; + +public class CraftShapedRecipe extends ShapedRecipe implements CraftRecipe { + // TODO: Could eventually use this to add a matches() method or some such + private ShapedRecipes recipe; + + public CraftShapedRecipe(NamespacedKey key, ItemStack result) { + super(key, result); + } + + public CraftShapedRecipe(ItemStack result, ShapedRecipes recipe) { + this(CraftNamespacedKey.fromMinecraft(recipe.key), result); + this.recipe = recipe; + } + + public static CraftShapedRecipe fromBukkitRecipe(ShapedRecipe recipe) { + if (recipe instanceof CraftShapedRecipe) { + return (CraftShapedRecipe) recipe; + } + CraftShapedRecipe ret = new CraftShapedRecipe(recipe.getKey(), recipe.getResult()); + String[] shape = recipe.getShape(); + ret.shape(shape); + Map ingredientMap = recipe.getIngredientMap(); + for (char c : ingredientMap.keySet()) { + ItemStack stack = ingredientMap.get(c); + if (stack != null) { + ret.setIngredient(c, stack.getType(), stack.getDurability()); + } + } + return ret; + } + + public void addToCraftingManager() { + String[] shape = this.getShape(); + Map ingred = this.getIngredientMap(); + int width = shape[0].length(); + NonNullList data = NonNullList.withSize(shape.length * width, Ingredient.EMPTY); + + for (int i = 0; i < shape.length; i++) { + String row = shape[i]; + for (int j = 0; j < row.length(); j++) { + data.set(i * width + j, Ingredient.fromStacks(new net.minecraft.item.ItemStack[]{CraftItemStack.asNMSCopy(ingred.get(row.charAt(j)))})); + } + } + // TODO: Check if it's correct way to register recipes + ForgeRegistries.RECIPES.register(new ShapedRecipes("", width, shape.length, data, CraftItemStack.asNMSCopy(this.getResult()))); + // CraftingManager.register(CraftNamespacedKey.toMinecraft(this.getKey()), new ShapedRecipes("", width, shape.length, data, CraftItemStack.asNMSCopy(this.getResult()))); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java new file mode 100644 index 00000000..01e982c2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.List; + +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapelessRecipes; +import net.minecraft.util.NonNullList; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ShapelessRecipe; + +public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe { + // TODO: Could eventually use this to add a matches() method or some such + private ShapelessRecipes recipe; + + public CraftShapelessRecipe(NamespacedKey key, ItemStack result) { + super(key, result); + } + + public CraftShapelessRecipe(ItemStack result, ShapelessRecipes recipe) { + this(CraftNamespacedKey.fromMinecraft(recipe.key), result); + this.recipe = recipe; + } + + public static CraftShapelessRecipe fromBukkitRecipe(ShapelessRecipe recipe) { + if (recipe instanceof CraftShapelessRecipe) { + return (CraftShapelessRecipe) recipe; + } + CraftShapelessRecipe ret = new CraftShapelessRecipe(recipe.getKey(), recipe.getResult()); + for (ItemStack ingred : recipe.getIngredientList()) { + ret.addIngredient(ingred.getType(), ingred.getDurability()); + } + return ret; + } + + public void addToCraftingManager() { + List ingred = this.getIngredientList(); + NonNullList data = NonNullList.withSize(ingred.size(), Ingredient.EMPTY); + for (int i = 0; i < ingred.size(); i++) { + data.set(i, Ingredient.fromStacks(new net.minecraft.item.ItemStack[]{CraftItemStack.asNMSCopy(ingred.get(i))})); + } + // TODO: Check if it's correct way to register recipes + ForgeRegistries.RECIPES.register(new ShapelessRecipes("", CraftItemStack.asNMSCopy(this.getResult()), data)); + // CraftingManager.a(CraftNamespacedKey.toMinecraft(this.getKey()), new ShapelessRecipes("", CraftItemStack.asNMSCopy(this.getResult()), data)); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/InventoryIterator.java b/src/main/java/org/bukkit/craftbukkit/inventory/InventoryIterator.java new file mode 100644 index 00000000..e3b5f42a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/InventoryIterator.java @@ -0,0 +1,64 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ListIterator; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class InventoryIterator implements ListIterator { + private final Inventory inventory; + private int nextIndex; + private Boolean lastDirection; // true = forward, false = backward, null = haven't moved yet + + InventoryIterator(Inventory craftInventory) { + this.inventory = craftInventory; + this.nextIndex = 0; + } + + InventoryIterator(Inventory craftInventory, int index) { + this.inventory = craftInventory; + this.nextIndex = index; + } + + public boolean hasNext() { + return nextIndex < inventory.getSize(); + } + + public ItemStack next() { + lastDirection = true; + return inventory.getItem(nextIndex++); + } + + public int nextIndex() { + return nextIndex; + } + + public boolean hasPrevious() { + return nextIndex > 0; + } + + public ItemStack previous() { + lastDirection = false; + return inventory.getItem(--nextIndex); + } + + public int previousIndex() { + return nextIndex - 1; + } + + public void set(ItemStack item) { + if (lastDirection == null) { + throw new IllegalStateException("No current item!"); + } + int i = lastDirection ? nextIndex - 1 : nextIndex; + inventory.setItem(i, item); + } + + public void add(ItemStack item) { + throw new UnsupportedOperationException("Can't change the size of an inventory!"); + } + + public void remove() { + throw new UnsupportedOperationException("Can't change the size of an inventory!"); + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/InventoryWrapper.java b/src/main/java/org/bukkit/craftbukkit/inventory/InventoryWrapper.java new file mode 100644 index 00000000..5425d4f6 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/InventoryWrapper.java @@ -0,0 +1,187 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +public class InventoryWrapper implements IInventory { + + private final Inventory inventory; + private final List viewers = new ArrayList(); + + public InventoryWrapper(Inventory inventory) { + this.inventory = inventory; + } + + @Override + public int getSizeInventory() { + return inventory.getSize(); + } + + @Override + public ItemStack getStackInSlot(int i) { + return CraftItemStack.asNMSCopy(inventory.getItem(i)); + } + + @Override + public ItemStack decrStackSize(int i, int j) { + // Copied from CraftItemStack + ItemStack stack = getStackInSlot(i); + ItemStack result; + if (stack.isEmpty()) { + return stack; + } + if (stack.getCount() <= j) { + this.setInventorySlotContents(i, ItemStack.EMPTY); + result = stack; + } else { + result = CraftItemStack.copyNMSStack(stack, j); + stack.shrink(j); + } + this.markDirty(); + return result; + } + + @Override + public ItemStack removeStackFromSlot(int i) { + // Copied from CraftItemStack + ItemStack stack = getStackInSlot(i); + ItemStack result; + if (stack.isEmpty()) { + return stack; + } + if (stack.getCount() <= 1) { + this.setInventorySlotContents(i, ItemStack.EMPTY); + result = stack; + } else { + result = CraftItemStack.copyNMSStack(stack, 1); + stack.shrink(1); + } + return result; + } + + @Override + public void setInventorySlotContents(int i, ItemStack itemstack) { + inventory.setItem(i, CraftItemStack.asBukkitCopy(itemstack)); + } + + @Override + public int getInventoryStackLimit() { + return inventory.getMaxStackSize(); + } + + @Override + public void markDirty() { + } + + @Override + public boolean isUsableByPlayer(EntityPlayer entityhuman) { + return true; + } + + @Override + public void openInventory(EntityPlayer entityhuman) { + } + + @Override + public void closeInventory(EntityPlayer entityhuman) { + } + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public int getField(int i) { + return 0; + } + + @Override + public void setField(int i, int j) { + } + + @Override + public int getFieldCount() { + return 0; + } + + @Override + public void clear() { + inventory.clear(); + } + + @Override + public List getContents() { + int size = getSizeInventory(); + List items = new ArrayList(size); + + for (int i = 0; i < size; i++) { + items.set(i, getStackInSlot(i)); + } + + return items; + } + + @Override + public void onOpen(CraftHumanEntity who) { + viewers.add(who); + } + + @Override + public void onClose(CraftHumanEntity who) { + viewers.remove(who); + } + + @Override + public List getViewers() { + return viewers; + } + + @Override + public InventoryHolder getOwner() { + return inventory.getHolder(); + } + + @Override + public void setMaxStackSize(int size) { + inventory.setMaxStackSize(size); + } + + @Override + public String getName() { + return inventory.getName(); + } + + @Override + public boolean hasCustomName() { + return getName() != null; + } + + @Override + public ITextComponent getDisplayName() { + return CraftChatMessage.fromString(getName())[0]; + } + + @Override + public Location getLocation() { + return inventory.getLocation(); + } + + @Override + public boolean isEmpty() { + return Iterables.any(inventory, Predicates.notNull()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java new file mode 100644 index 00000000..daab0f8e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java @@ -0,0 +1,59 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Iterator; + +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.item.crafting.IRecipe; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; + +public class RecipeIterator implements Iterator { + private final Iterator recipes; + private final Iterator smeltingCustom; + private final Iterator smeltingVanilla; + private Iterator removeFrom = null; + + public RecipeIterator() { + this.recipes = CraftingManager.REGISTRY.iterator(); + this.smeltingCustom = FurnaceRecipes.instance().customRecipes.keySet().iterator(); + this.smeltingVanilla = FurnaceRecipes.instance().smeltingList.keySet().iterator(); + } + + public boolean hasNext() { + return recipes.hasNext() || smeltingCustom.hasNext() || smeltingVanilla.hasNext(); + } + + public Recipe next() { + if (recipes.hasNext()) { + removeFrom = recipes; + IRecipe recipe = recipes.next(); + if (recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe) { + return recipe.toBukkitRecipe(); + } else { + return new CraftCustomModRecipe(recipe); + } + } else { + net.minecraft.item.ItemStack item; + if (smeltingCustom.hasNext()) { + removeFrom = smeltingCustom; + item = smeltingCustom.next(); + } else { + removeFrom = smeltingVanilla; + item = smeltingVanilla.next(); + } + + CraftItemStack stack = CraftItemStack.asCraftMirror(FurnaceRecipes.instance().getSmeltingResult(item)); + + return new CraftFurnaceRecipe(stack, CraftItemStack.asCraftMirror(item)); + } + } + + public void remove() { + if (removeFrom == null) { + throw new IllegalStateException(); + } + removeFrom.remove(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java new file mode 100644 index 00000000..e59097c8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java @@ -0,0 +1,111 @@ +package org.bukkit.craftbukkit.map; + +import java.awt.Image; +import java.util.Arrays; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapCursorCollection; +import org.bukkit.map.MapFont; +import org.bukkit.map.MapFont.CharacterSprite; +import org.bukkit.map.MapPalette; + +public class CraftMapCanvas implements MapCanvas { + + private final byte[] buffer = new byte[128 * 128]; + private final CraftMapView mapView; + private byte[] base; + private MapCursorCollection cursors = new MapCursorCollection(); + + protected CraftMapCanvas(CraftMapView mapView) { + this.mapView = mapView; + Arrays.fill(buffer, (byte) -1); + } + + public CraftMapView getMapView() { + return mapView; + } + + public MapCursorCollection getCursors() { + return cursors; + } + + public void setCursors(MapCursorCollection cursors) { + this.cursors = cursors; + } + + public void setPixel(int x, int y, byte color) { + if (x < 0 || y < 0 || x >= 128 || y >= 128) + return; + if (buffer[y * 128 + x] != color) { + buffer[y * 128 + x] = color; + mapView.worldMap.updateMapData(x, y); + } + } + + public byte getPixel(int x, int y) { + if (x < 0 || y < 0 || x >= 128 || y >= 128) + return 0; + return buffer[y * 128 + x]; + } + + public byte getBasePixel(int x, int y) { + if (x < 0 || y < 0 || x >= 128 || y >= 128) + return 0; + return base[y * 128 + x]; + } + + protected void setBase(byte[] base) { + this.base = base; + } + + protected byte[] getBuffer() { + return buffer; + } + + public void drawImage(int x, int y, Image image) { + byte[] bytes = MapPalette.imageToBytes(image); + for (int x2 = 0; x2 < image.getWidth(null); ++x2) { + for (int y2 = 0; y2 < image.getHeight(null); ++y2) { + setPixel(x + x2, y + y2, bytes[y2 * image.getWidth(null) + x2]); + } + } + } + + public void drawText(int x, int y, MapFont font, String text) { + int xStart = x; + byte color = MapPalette.DARK_GRAY; + if (!font.isValid(text)) { + throw new IllegalArgumentException("text contains invalid characters"); + } + + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == '\n') { + x = xStart; + y += font.getHeight() + 1; + continue; + } else if (ch == '\u00A7') { + int j = text.indexOf(';', i); + if (j >= 0) { + try { + color = Byte.parseByte(text.substring(i + 1, j)); + i = j; + continue; + } + catch (NumberFormatException ex) {} + } + throw new IllegalArgumentException("Text contains unterminated color string"); + } + + CharacterSprite sprite = font.getChar(text.charAt(i)); + for (int r = 0; r < font.getHeight(); ++r) { + for (int c = 0; c < sprite.getWidth(); ++c) { + if (sprite.get(r, c)) { + setPixel(x + c, y + r, color); + } + } + } + x += sprite.getWidth() + 1; + } + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java new file mode 100644 index 00000000..ef45215c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.map; + +import net.minecraft.world.storage.MapData; +import net.minecraft.world.storage.MapDecoration; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapCursorCollection; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; + +public class CraftMapRenderer extends MapRenderer { + + private final MapData worldMap; + + public CraftMapRenderer(CraftMapView mapView, MapData worldMap) { + super(false); + this.worldMap = worldMap; + } + + @Override + public void render(MapView map, MapCanvas canvas, Player player) { + // Map + for (int x = 0; x < 128; ++x) { + for (int y = 0; y < 128; ++y) { + canvas.setPixel(x, y, worldMap.colors[y * 128 + x]); + } + } + + // Cursors + MapCursorCollection cursors = canvas.getCursors(); + while (cursors.size() > 0) { + cursors.removeCursor(cursors.getCursor(0)); + } + + for (Object key : worldMap.mapDecorations.keySet()) { + // If this cursor is for a player check visibility with vanish system + Player other = Bukkit.getPlayerExact((String) key); + if (other != null && !player.canSee(other)) { + continue; + } + + MapDecoration decoration = worldMap.mapDecorations.get(key); + cursors.addCursor(decoration.getX(), decoration.getY(), (byte) (decoration.getRotation() & 15), decoration.getImage()); + } + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java new file mode 100644 index 00000000..ccb9cbc3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapView.java @@ -0,0 +1,176 @@ +package org.bukkit.craftbukkit.map; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import net.minecraft.world.storage.MapData; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; + +public final class CraftMapView implements MapView { + + private final Map renderCache = new HashMap(); + private final List renderers = new ArrayList(); + private final Map> canvases = new HashMap>(); + protected final MapData worldMap; + + public CraftMapView(MapData worldMap) { + this.worldMap = worldMap; + addRenderer(new CraftMapRenderer(this, worldMap)); + } + + public short getId() { + String text = worldMap.mapName; + if (text.startsWith("map_")) { + try { + return Short.parseShort(text.substring("map_".length())); + } + catch (NumberFormatException ex) { + throw new IllegalStateException("Map has non-numeric ID"); + } + } else { + throw new IllegalStateException("Map has invalid ID"); + } + } + + public boolean isVirtual() { + return renderers.size() > 0 && !(renderers.get(0) instanceof CraftMapRenderer); + } + + public Scale getScale() { + return Scale.valueOf(worldMap.scale); + } + + public void setScale(Scale scale) { + worldMap.scale = scale.getValue(); + } + + public World getWorld() { + int dimension = worldMap.dimension; + for (World world : Bukkit.getServer().getWorlds()) { + if (((CraftWorld) world).getHandle().dimension == dimension) { + return world; + } + } + return null; + } + + public void setWorld(World world) { + worldMap.dimension = (byte) ((CraftWorld) world).getHandle().dimension; + } + + public int getCenterX() { + return worldMap.xCenter; + } + + public int getCenterZ() { + return worldMap.zCenter; + } + + public void setCenterX(int x) { + worldMap.xCenter = x; + } + + public void setCenterZ(int z) { + worldMap.zCenter = z; + } + + public List getRenderers() { + return new ArrayList(renderers); + } + + public void addRenderer(MapRenderer renderer) { + if (!renderers.contains(renderer)) { + renderers.add(renderer); + canvases.put(renderer, new HashMap()); + renderer.initialize(this); + } + } + + public boolean removeRenderer(MapRenderer renderer) { + if (renderers.contains(renderer)) { + renderers.remove(renderer); + for (Map.Entry entry : canvases.get(renderer).entrySet()) { + for (int x = 0; x < 128; ++x) { + for (int y = 0; y < 128; ++y) { + entry.getValue().setPixel(x, y, (byte) -1); + } + } + } + canvases.remove(renderer); + return true; + } else { + return false; + } + } + + private boolean isContextual() { + for (MapRenderer renderer : renderers) { + if (renderer.isContextual()) return true; + } + return false; + } + + public RenderData render(CraftPlayer player) { + boolean context = isContextual(); + RenderData render = renderCache.get(context ? player : null); + + if (render == null) { + render = new RenderData(); + renderCache.put(context ? player : null, render); + } + + if (context && renderCache.containsKey(null)) { + renderCache.remove(null); + } + + Arrays.fill(render.buffer, (byte) 0); + render.cursors.clear(); + + for (MapRenderer renderer : renderers) { + CraftMapCanvas canvas = canvases.get(renderer).get(renderer.isContextual() ? player : null); + if (canvas == null) { + canvas = new CraftMapCanvas(this); + canvases.get(renderer).put(renderer.isContextual() ? player : null, canvas); + } + + canvas.setBase(render.buffer); + try { + renderer.render(this, canvas, player); + } catch (Throwable ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not render map using renderer " + renderer.getClass().getName(), ex); + } + + byte[] buf = canvas.getBuffer(); + for (int i = 0; i < buf.length; ++i) { + byte color = buf[i]; + // There are 208 valid color id's, 0 -> 127 and -128 -> -49 + if (color >= 0 || color <= -49) render.buffer[i] = color; + } + + for (int i = 0; i < canvas.getCursors().size(); ++i) { + render.cursors.add(canvas.getCursors().getCursor(i)); + } + } + + return render; + } + + @Override + public boolean isUnlimitedTracking() { + return worldMap.unlimitedTracking; + } + + @Override + public void setUnlimitedTracking(boolean unlimited) { + worldMap.unlimitedTracking = unlimited; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java new file mode 100644 index 00000000..256a1317 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java @@ -0,0 +1,16 @@ +package org.bukkit.craftbukkit.map; + +import java.util.ArrayList; +import org.bukkit.map.MapCursor; + +public class RenderData { + + public final byte[] buffer; + public final ArrayList cursors; + + public RenderData() { + this.buffer = new byte[128 * 128]; + this.cursors = new ArrayList(); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/metadata/BlockMetadataStore.java b/src/main/java/org/bukkit/craftbukkit/metadata/BlockMetadataStore.java new file mode 100644 index 00000000..544cf1c8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/metadata/BlockMetadataStore.java @@ -0,0 +1,94 @@ +package org.bukkit.craftbukkit.metadata; + +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.metadata.MetadataStore; +import org.bukkit.metadata.MetadataStoreBase; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +import java.util.List; + +/** + * A BlockMetadataStore stores metadata values for {@link Block} objects. + */ +public class BlockMetadataStore extends MetadataStoreBase implements MetadataStore { + + private final World owningWorld; + + /** + * Initializes a BlockMetadataStore. + * @param owningWorld The world to which this BlockMetadataStore belongs. + */ + public BlockMetadataStore(World owningWorld) { + this.owningWorld = owningWorld; + } + + /** + * Generates a unique metadata key for a {@link Block} object based on its coordinates in the world. + * @see MetadataStoreBase#disambiguate(Object, String) + * @param block the block + * @param metadataKey The name identifying the metadata value + * @return a unique metadata key + */ + @Override + protected String disambiguate(Block block, String metadataKey) { + return Integer.toString(block.getX()) + ":" + Integer.toString(block.getY()) + ":" + Integer.toString(block.getZ()) + ":" + metadataKey; + } + + /** + * Retrieves the metadata for a {@link Block}, ensuring the block being asked for actually belongs to this BlockMetadataStore's + * owning world. + * @see MetadataStoreBase#getMetadata(Object, String) + */ + @Override + public List getMetadata(Block block, String metadataKey) { + if(block.getWorld() == owningWorld) { + return super.getMetadata(block, metadataKey); + } else { + throw new IllegalArgumentException("Block does not belong to world " + owningWorld.getName()); + } + } + + /** + * Tests to see if a metadata value has been added to a {@link Block}, ensuring the block being interrogated belongs + * to this BlockMetadataStore's owning world. + * @see MetadataStoreBase#hasMetadata(Object, String) + */ + @Override + public boolean hasMetadata(Block block, String metadataKey) { + if(block.getWorld() == owningWorld) { + return super.hasMetadata(block, metadataKey); + } else { + throw new IllegalArgumentException("Block does not belong to world " + owningWorld.getName()); + } + } + + /** + * Removes metadata from from a {@link Block} belonging to a given {@link Plugin}, ensuring the block being deleted from belongs + * to this BlockMetadataStore's owning world. + * @see MetadataStoreBase#removeMetadata(Object, String, Plugin) + */ + @Override + public void removeMetadata(Block block, String metadataKey, Plugin owningPlugin) { + if(block.getWorld() == owningWorld) { + super.removeMetadata(block, metadataKey, owningPlugin); + } else { + throw new IllegalArgumentException("Block does not belong to world " + owningWorld.getName()); + } + } + + /** + * Sets or overwrites a metadata value on a {@link Block} from a given {@link Plugin}, ensuring the target block belongs + * to this BlockMetadataStore's owning world. + * @see MetadataStoreBase#setMetadata(Object, String, MetadataValue) + */ + @Override + public void setMetadata(Block block, String metadataKey, MetadataValue newMetadataValue) { + if(block.getWorld() == owningWorld) { + super.setMetadata(block, metadataKey, newMetadataValue); + } else { + throw new IllegalArgumentException("Block does not belong to world " + owningWorld.getName()); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/metadata/EntityMetadataStore.java b/src/main/java/org/bukkit/craftbukkit/metadata/EntityMetadataStore.java new file mode 100644 index 00000000..35c484f4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/metadata/EntityMetadataStore.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.metadata; + +import org.bukkit.entity.Entity; +import org.bukkit.metadata.MetadataStore; +import org.bukkit.metadata.MetadataStoreBase; + +/** + * An EntityMetadataStore stores metadata values for all {@link Entity} classes an their descendants. + */ +public class EntityMetadataStore extends MetadataStoreBase implements MetadataStore { + /** + * Generates a unique metadata key for an {@link Entity} UUID. + * + * @see MetadataStoreBase#disambiguate(Object, String) + * @param entity the entity + * @param metadataKey The name identifying the metadata value + * @return a unique metadata key + */ + @Override + protected String disambiguate(Entity entity, String metadataKey) { + return entity.getUniqueId().toString() + ":" + metadataKey; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/metadata/PlayerMetadataStore.java b/src/main/java/org/bukkit/craftbukkit/metadata/PlayerMetadataStore.java new file mode 100644 index 00000000..f4010b3a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/metadata/PlayerMetadataStore.java @@ -0,0 +1,23 @@ +package org.bukkit.craftbukkit.metadata; + +import org.bukkit.OfflinePlayer; +import org.bukkit.metadata.MetadataStore; +import org.bukkit.metadata.MetadataStoreBase; + +/** + * A PlayerMetadataStore stores metadata for {@link org.bukkit.entity.Player} and {@link OfflinePlayer} objects. + */ +public class PlayerMetadataStore extends MetadataStoreBase implements MetadataStore { + /** + * Generates a unique metadata key for {@link org.bukkit.entity.Player} and {@link OfflinePlayer} using the player + * name. + * @see MetadataStoreBase#disambiguate(Object, String) + * @param player the player + * @param metadataKey The name identifying the metadata value + * @return a unique metadata key + */ + @Override + protected String disambiguate(OfflinePlayer player, String metadataKey) { + return player.getName().toLowerCase(java.util.Locale.ENGLISH) + ":" + metadataKey; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/metadata/WorldMetadataStore.java b/src/main/java/org/bukkit/craftbukkit/metadata/WorldMetadataStore.java new file mode 100644 index 00000000..dd37ed29 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/metadata/WorldMetadataStore.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.metadata; + +import org.bukkit.World; +import org.bukkit.metadata.MetadataStore; +import org.bukkit.metadata.MetadataStoreBase; + +/** + * An WorldMetadataStore stores metadata values for {@link World} objects. + */ +public class WorldMetadataStore extends MetadataStoreBase implements MetadataStore { + /** + * Generates a unique metadata key for a {@link World} object based on the world UID. + * @see WorldMetadataStore#disambiguate(Object, String) + * @param world the world + * @param metadataKey The name identifying the metadata value + * @return a unique metadata key + */ + @Override + protected String disambiguate(World world, String metadataKey) { + return world.getUID().toString() + ":" + metadataKey; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java new file mode 100644 index 00000000..a61dbd4a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java @@ -0,0 +1,45 @@ +package org.bukkit.craftbukkit.potion; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.bukkit.potion.PotionBrewer; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; + +public class CraftPotionBrewer implements PotionBrewer { + private static final Map> cache = Maps.newHashMap(); + + public Collection getEffects(PotionType damage, boolean upgraded, boolean extended) { + if (cache.containsKey(damage)) + return cache.get(damage); + + List mcEffects = net.minecraft.potion.PotionType.getPotionTypeForName(CraftPotionUtil.fromBukkit(new PotionData(damage, extended, upgraded))).getEffects(); + + ImmutableList.Builder builder = new ImmutableList.Builder(); + for (net.minecraft.potion.PotionEffect effect : mcEffects) { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + + cache.put(damage, builder.build()); + + return cache.get(damage); + } + + @Override + public Collection getEffectsFromDamage(int damage) { + return new ArrayList(); + } + + @Override + public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) { + return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java new file mode 100644 index 00000000..3213928a --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java @@ -0,0 +1,95 @@ +package org.bukkit.craftbukkit.potion; + +import net.minecraft.potion.Potion; +import org.bukkit.Color; +import org.bukkit.potion.PotionEffectType; + +public class CraftPotionEffectType extends PotionEffectType { + private final Potion handle; + + public CraftPotionEffectType(Potion handle) { + super(Potion.getIdFromPotion(handle)); + this.handle = handle; + } + + @Override + public double getDurationModifier() { + return handle.effectiveness; + } + + public Potion getHandle() { + return handle; + } + + @Override + public String getName() { + switch (getId()) { + case 1: + return "SPEED"; + case 2: + return "SLOW"; + case 3: + return "FAST_DIGGING"; + case 4: + return "SLOW_DIGGING"; + case 5: + return "INCREASE_DAMAGE"; + case 6: + return "HEAL"; + case 7: + return "HARM"; + case 8: + return "JUMP"; + case 9: + return "CONFUSION"; + case 10: + return "REGENERATION"; + case 11: + return "DAMAGE_RESISTANCE"; + case 12: + return "FIRE_RESISTANCE"; + case 13: + return "WATER_BREATHING"; + case 14: + return "INVISIBILITY"; + case 15: + return "BLINDNESS"; + case 16: + return "NIGHT_VISION"; + case 17: + return "HUNGER"; + case 18: + return "WEAKNESS"; + case 19: + return "POISON"; + case 20: + return "WITHER"; + case 21: + return "HEALTH_BOOST"; + case 22: + return "ABSORPTION"; + case 23: + return "SATURATION"; + case 24: + return "GLOWING"; + case 25: + return "LEVITATION"; + case 26: + return "LUCK"; + case 27: + return "UNLUCK"; + default: + return "UNKNOWN_EFFECT_TYPE_" + getId(); + } + } + + @Override + public boolean isInstant() { + return handle.isInstant(); + } + + @Override + public Color getColor() { + return Color.fromRGB(handle.getLiquidColor()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java new file mode 100644 index 00000000..7efec186 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java @@ -0,0 +1,115 @@ +package org.bukkit.craftbukkit.potion; + +import com.google.common.base.Preconditions; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; + +import net.minecraft.potion.Potion; + +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.bukkit.potion.PotionData; + +public class CraftPotionUtil { + + private static final BiMap regular = ImmutableBiMap.builder() + .put(PotionType.UNCRAFTABLE, "empty") + .put(PotionType.WATER, "water") + .put(PotionType.MUNDANE, "mundane") + .put(PotionType.THICK, "thick") + .put(PotionType.AWKWARD, "awkward") + .put(PotionType.NIGHT_VISION, "night_vision") + .put(PotionType.INVISIBILITY, "invisibility") + .put(PotionType.JUMP, "leaping") + .put(PotionType.FIRE_RESISTANCE, "fire_resistance") + .put(PotionType.SPEED, "swiftness") + .put(PotionType.SLOWNESS, "slowness") + .put(PotionType.WATER_BREATHING, "water_breathing") + .put(PotionType.INSTANT_HEAL, "healing") + .put(PotionType.INSTANT_DAMAGE, "harming") + .put(PotionType.POISON, "poison") + .put(PotionType.REGEN, "regeneration") + .put(PotionType.STRENGTH, "strength") + .put(PotionType.WEAKNESS, "weakness") + .put(PotionType.LUCK, "luck") + .build(); + private static final BiMap upgradeable = ImmutableBiMap.builder() + .put(PotionType.JUMP, "strong_leaping") + .put(PotionType.SPEED, "strong_swiftness") + .put(PotionType.INSTANT_HEAL, "strong_healing") + .put(PotionType.INSTANT_DAMAGE, "strong_harming") + .put(PotionType.POISON, "strong_poison") + .put(PotionType.REGEN, "strong_regeneration") + .put(PotionType.STRENGTH, "strong_strength") + .build(); + private static final BiMap extendable = ImmutableBiMap.builder() + .put(PotionType.NIGHT_VISION, "long_night_vision") + .put(PotionType.INVISIBILITY, "long_invisibility") + .put(PotionType.JUMP, "long_leaping") + .put(PotionType.FIRE_RESISTANCE, "long_fire_resistance") + .put(PotionType.SPEED, "long_swiftness") + .put(PotionType.SLOWNESS, "long_slowness") + .put(PotionType.WATER_BREATHING, "long_water_breathing") + .put(PotionType.POISON, "long_poison") + .put(PotionType.REGEN, "long_regeneration") + .put(PotionType.STRENGTH, "long_strength") + .put(PotionType.WEAKNESS, "long_weakness") + .build(); + + public static String fromBukkit(PotionData data) { + String type; + if (data.isUpgraded()) { + type = upgradeable.get(data.getType()); + } else if (data.isExtended()) { + type = extendable.get(data.getType()); + } else { + type = regular.get(data.getType()); + } + Preconditions.checkNotNull(type, "Unknown potion type from data " + data); + + return "minecraft:" + type; + } + + public static PotionData toBukkit(String type) { + if (type == null) { + return new PotionData(PotionType.UNCRAFTABLE, false, false); + } + if (type.startsWith("minecraft:")) { + type = type.substring(10); + } + PotionType potionType = null; + potionType = extendable.inverse().get(type); + if (potionType != null) { + return new PotionData(potionType, true, false); + } + potionType = upgradeable.inverse().get(type); + if (potionType != null) { + return new PotionData(potionType, false, true); + } + potionType = regular.inverse().get(type); + if (potionType != null) { + return new PotionData(potionType, false, false); + } + return new PotionData(PotionType.UNCRAFTABLE, false, false); + } + + public static net.minecraft.potion.PotionEffect fromBukkit(PotionEffect effect) { + Potion type = Potion.getPotionById(effect.getType().getId()); + return new net.minecraft.potion.PotionEffect(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); + } + + public static PotionEffect toBukkit(net.minecraft.potion.PotionEffect effect) { + PotionEffectType type = PotionEffectType.getById(Potion.getIdFromPotion(effect.getPotion())); + int amp = effect.getAmplifier(); + int duration = effect.getDuration(); + boolean ambient = effect.getIsAmbient(); + boolean particles = effect.doesShowParticles(); + return new PotionEffect(type, duration, amp, ambient, particles); + } + + public static boolean equals(Potion mobEffect, PotionEffectType type) { + PotionEffectType typeV = PotionEffectType.getById(Potion.getIdFromPotion(mobEffect)); + return typeV.equals(type); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java new file mode 100644 index 00000000..0d7a815d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java @@ -0,0 +1,161 @@ +package org.bukkit.craftbukkit.projectiles; + +import java.util.Random; + +import net.minecraft.block.BlockDispenser; +import net.minecraft.block.BlockSourceImpl; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.dispenser.IPosition; +import net.minecraft.entity.IProjectile; +import net.minecraft.entity.item.EntityEnderPearl; +import net.minecraft.entity.item.EntityExpBottle; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityEgg; +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.entity.projectile.EntityLargeFireball; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.entity.projectile.EntitySmallFireball; +import net.minecraft.entity.projectile.EntitySnowball; +import net.minecraft.entity.projectile.EntitySpectralArrow; +import net.minecraft.entity.projectile.EntityThrowable; +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.entity.projectile.EntityWitherSkull; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.MathHelper; +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Egg; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.LingeringPotion; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.SmallFireball; +import org.bukkit.entity.Snowball; +import org.bukkit.entity.SpectralArrow; +import org.bukkit.entity.ThrownExpBottle; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.TippedArrow; +import org.bukkit.entity.WitherSkull; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionType; +import org.bukkit.projectiles.BlockProjectileSource; +import org.bukkit.util.Vector; + +public class CraftBlockProjectileSource implements BlockProjectileSource { + private final TileEntityDispenser dispenserBlock; + + public CraftBlockProjectileSource(TileEntityDispenser dispenserBlock) { + this.dispenserBlock = dispenserBlock; + } + + @Override + public Block getBlock() { + return dispenserBlock.getWorld().getWorld().getBlockAt(dispenserBlock.getPos().getX(), dispenserBlock.getPos().getY(), dispenserBlock.getPos().getZ()); + } + + @Override + public T launchProjectile(Class projectile) { + return launchProjectile(projectile, null); + } + + @Override + public T launchProjectile(Class projectile, Vector velocity) { + Validate.isTrue(getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser"); + // Copied from BlockDispenser.dispense() + IBlockSource isourceblock = new BlockSourceImpl(dispenserBlock.getWorld(), dispenserBlock.getPos()); + // Copied from DispenseBehaviorProjectile + IPosition iposition = BlockDispenser.getDispensePosition(isourceblock); + EnumFacing enumdirection = (EnumFacing) isourceblock.getBlockState().getValue(BlockDispenser.FACING); + net.minecraft.world.World world = dispenserBlock.getWorld(); + net.minecraft.entity.Entity launch = null; + + if (Snowball.class.isAssignableFrom(projectile)) { + launch = new EntitySnowball(world, iposition.getX(), iposition.getY(), iposition.getZ()); + } else if (Egg.class.isAssignableFrom(projectile)) { + launch = new EntityEgg(world, iposition.getX(), iposition.getY(), iposition.getZ()); + } else if (EnderPearl.class.isAssignableFrom(projectile)) { + launch = new EntityEnderPearl(world, null); + launch.setPosition(iposition.getX(), iposition.getY(), iposition.getZ()); + } else if (ThrownExpBottle.class.isAssignableFrom(projectile)) { + launch = new EntityExpBottle(world, iposition.getX(), iposition.getY(), iposition.getZ()); + } else if (ThrownPotion.class.isAssignableFrom(projectile)) { + if (LingeringPotion.class.isAssignableFrom(projectile)) { + launch = new EntityPotion(world, iposition.getX(), iposition.getY(), iposition.getZ(), CraftItemStack.asNMSCopy(new ItemStack(Material.LINGERING_POTION, 1))); + } else { + launch = new EntityPotion(world, iposition.getX(), iposition.getY(), iposition.getZ(), CraftItemStack.asNMSCopy(new ItemStack(Material.SPLASH_POTION, 1))); + } + } else if (Arrow.class.isAssignableFrom(projectile)) { + if (TippedArrow.class.isAssignableFrom(projectile)) { + launch = new EntityTippedArrow(world, iposition.getX(), iposition.getY(), iposition.getZ()); + ((EntityTippedArrow) launch).setType(CraftPotionUtil.fromBukkit(new PotionData(PotionType.WATER, false, false))); + } else if (SpectralArrow.class.isAssignableFrom(projectile)) { + launch = new EntitySpectralArrow(world, iposition.getX(), iposition.getY(), iposition.getZ()); + } else { + launch = new EntityTippedArrow(world, iposition.getX(), iposition.getY(), iposition.getZ()); + } + ((EntityArrow) launch).pickupStatus = EntityArrow.PickupStatus.ALLOWED; + ((EntityArrow) launch).projectileSource = this; + } else if (Fireball.class.isAssignableFrom(projectile)) { + double d0 = iposition.getX() + (double) ((float) enumdirection.getFrontOffsetX() * 0.3F); + double d1 = iposition.getY() + (double) ((float) enumdirection.getFrontOffsetY() * 0.3F); + double d2 = iposition.getZ() + (double) ((float) enumdirection.getFrontOffsetZ() * 0.3F); + Random random = world.rand; + double d3 = random.nextGaussian() * 0.05D + (double) enumdirection.getFrontOffsetX(); + double d4 = random.nextGaussian() * 0.05D + (double) enumdirection.getFrontOffsetY(); + double d5 = random.nextGaussian() * 0.05D + (double) enumdirection.getFrontOffsetZ(); + + if (SmallFireball.class.isAssignableFrom(projectile)) { + launch = new EntitySmallFireball(world, null, d0, d1, d2); + } else if (WitherSkull.class.isAssignableFrom(projectile)) { + launch = new EntityWitherSkull(world); + launch.setPosition(d0, d1, d2); + double d6 = (double) MathHelper.sqrt(d3 * d3 + d4 * d4 + d5 * d5); + + ((EntityFireball) launch).accelerationX = d3 / d6 * 0.1D; + ((EntityFireball) launch).accelerationY = d4 / d6 * 0.1D; + ((EntityFireball) launch).accelerationZ = d5 / d6 * 0.1D; + } else { + launch = new EntityLargeFireball(world); + launch.setPosition(d0, d1, d2); + double d6 = (double) MathHelper.sqrt(d3 * d3 + d4 * d4 + d5 * d5); + + ((EntityFireball) launch).accelerationX = d3 / d6 * 0.1D; + ((EntityFireball) launch).accelerationY = d4 / d6 * 0.1D; + ((EntityFireball) launch).accelerationZ = d5 / d6 * 0.1D; + } + + ((EntityFireball) launch).projectileSource = this; + } + + Validate.notNull(launch, "Projectile not supported"); + + if (launch instanceof IProjectile) { + if (launch instanceof EntityThrowable) { + ((EntityThrowable) launch).projectileSource = this; + } + // Values from DispenseBehaviorProjectile + float a = 6.0F; + float b = 1.1F; + if (launch instanceof EntityPotion || launch instanceof ThrownExpBottle) { + // Values from respective DispenseBehavior classes + a *= 0.5F; + b *= 1.25F; + } + // Copied from DispenseBehaviorProjectile + ((IProjectile) launch).shoot((double) enumdirection.getFrontOffsetX(), (double) ((float) enumdirection.getFrontOffsetY() + 0.1F), (double) enumdirection.getFrontOffsetZ(), b, a); + } + + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } + + world.spawnEntity(launch); + return (T) launch.getBukkitEntity(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java new file mode 100644 index 00000000..d80ae50d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.scheduler; + +import org.bukkit.plugin.Plugin; + + +class CraftAsyncDebugger { + private CraftAsyncDebugger next = null; + private final int expiry; + private final Plugin plugin; + private final Class clazz; + + CraftAsyncDebugger(final int expiry, final Plugin plugin, final Class clazz) { + this.expiry = expiry; + this.plugin = plugin; + this.clazz = clazz; + + } + + final CraftAsyncDebugger getNextHead(final int time) { + CraftAsyncDebugger next, current = this; + while (time > current.expiry && (next = current.next) != null) { + current = next; + } + return current; + } + + final CraftAsyncDebugger setNext(final CraftAsyncDebugger next) { + return this.next = next; + } + + StringBuilder debugTo(final StringBuilder string) { + for (CraftAsyncDebugger next = this; next != null; next = next.next) { + string.append(next.plugin.getDescription().getName()).append(':').append(next.clazz.getName()).append('@').append(next.expiry).append(','); + } + return string; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java new file mode 100644 index 00000000..eaf86928 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy 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 copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.bukkit.craftbukkit.scheduler; + +import com.destroystokyo.paper.ServerSchedulerReportingWrapper; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class CraftAsyncScheduler extends CraftScheduler { + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), + new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("Craft Async Scheduler Management Thread").build()); + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { + super(true); + executor.allowCoreThreadTimeOut(true); + executor.prestartAllCoreThreads(); + } + + @Override + public void cancelTask(int taskId) { + this.management.execute(() -> this.removeTask(taskId)); + } + + private synchronized void removeTask(int taskId) { + parsePending(); + this.pending.removeIf((task) -> { + if (task.getTaskId() == taskId) { + task.cancel0(); + return true; + } + return false; + }); + } + + @Override + public void mainThreadHeartbeat(int currentTick) { + this.currentTick = currentTick; + this.management.execute(() -> this.runTasks(currentTick)); + } + + private synchronized void runTasks(int currentTick) { + parsePending(); + while (!this.pending.isEmpty() && this.pending.peek().getNextRun() <= currentTick) { + CraftTask task = this.pending.remove(); + if (executeTask(task)) { + final long period = task.getPeriod(); + if (period > 0) { + task.setNextRun(currentTick + period); + temp.add(task); + } + } + parsePending(); + } + this.pending.addAll(temp); + temp.clear(); + } + + private boolean executeTask(CraftTask task) { + if (isValid(task)) { + this.runners.put(task.getTaskId(), task); + this.executor.execute(new ServerSchedulerReportingWrapper(task)); + return true; + } + return false; + } + + @Override + public synchronized void cancelTasks(Plugin plugin) { + parsePending(); + for (Iterator iterator = this.pending.iterator(); iterator.hasNext(); ) { + CraftTask task = iterator.next(); + if (task.getTaskId() != -1 && (plugin == null || task.getOwner().equals(plugin))) { + task.cancel0(); + iterator.remove(); + } + } + } + + @Override + public synchronized void cancelAllTasks() { + cancelTasks(null); + } + + /** + * Task is not cancelled + * @param runningTask + * @return + */ + static boolean isValid(CraftTask runningTask) { + return runningTask.getPeriod() >= CraftTask.NO_REPEATING; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java new file mode 100644 index 00000000..fb50a4a7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java @@ -0,0 +1,109 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitWorker; + +class CraftAsyncTask extends CraftTask { + + private final LinkedList workers = new LinkedList(); + private final Map runners; + + CraftAsyncTask(final Map runners, final Plugin plugin, final Runnable task, final int id, final long delay) { + super(plugin, task, id, delay); + this.runners = runners; + } + + @Override + public boolean isSync() { + return false; + } + + @Override + public void run() { + final Thread thread = Thread.currentThread(); + synchronized(workers) { + if (getPeriod() == CraftTask.CANCEL) { + // Never continue running after cancelled. + // Checking this with the lock is important! + return; + } + workers.add( + new BukkitWorker() { + public Thread getThread() { + return thread; + } + + public int getTaskId() { + return CraftAsyncTask.this.getTaskId(); + } + + public Plugin getOwner() { + return CraftAsyncTask.this.getOwner(); + } + }); + } + Throwable thrown = null; + try { + super.run(); + } catch (final Throwable t) { + thrown = t; + getOwner().getLogger().log( + Level.WARNING, + String.format( + "Plugin %s generated an exception while executing task %s", + getOwner().getDescription().getFullName(), + getTaskId()), + thrown); + } finally { + // Cleanup is important for any async task, otherwise ghost tasks are everywhere + synchronized(workers) { + try { + final Iterator workers = this.workers.iterator(); + boolean removed = false; + while (workers.hasNext()) { + if (workers.next().getThread() == thread) { + workers.remove(); + removed = true; // Don't throw exception + break; + } + } + if (!removed) { + throw new IllegalStateException( + String.format( + "Unable to remove worker %s on task %s for %s", + thread.getName(), + getTaskId(), + getOwner().getDescription().getFullName()), + thrown); // We don't want to lose the original exception, if any + } + } finally { + if (getPeriod() < 0 && workers.isEmpty()) { + // At this spot, we know we are the final async task being executed! + // Because we have the lock, nothing else is running or will run because delay < 0 + runners.remove(getTaskId()); + } + } + } + } + } + + LinkedList getWorkers() { + return workers; + } + + boolean cancel0() { + synchronized (workers) { + // Synchronizing here prevents race condition for a completing task + setPeriod(CraftTask.CANCEL); + if (workers.isEmpty()) { + runners.remove(getTaskId()); + } + } + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java new file mode 100644 index 00000000..bb990e51 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftFuture.java @@ -0,0 +1,104 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.bukkit.plugin.Plugin; + +class CraftFuture extends CraftTask implements Future { + + private final Callable callable; + private T value; + private Exception exception = null; + + CraftFuture(final Callable callable, final Plugin plugin, final int id) { + super(plugin, null, id, CraftTask.NO_REPEATING); + this.callable = callable; + } + + public synchronized boolean cancel(final boolean mayInterruptIfRunning) { + if (getPeriod() != CraftTask.NO_REPEATING) { + return false; + } + setPeriod(CraftTask.CANCEL); + return true; + } + + public boolean isDone() { + final long period = this.getPeriod(); + return period != CraftTask.NO_REPEATING && period != CraftTask.PROCESS_FOR_FUTURE; + } + + public T get() throws CancellationException, InterruptedException, ExecutionException { + try { + return get(0, TimeUnit.MILLISECONDS); + } catch (final TimeoutException e) { + throw new Error(e); + } + } + + public synchronized T get(long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + timeout = unit.toMillis(timeout); + long period = this.getPeriod(); + long timestamp = timeout > 0 ? System.currentTimeMillis() : 0L; + while (true) { + if (period == CraftTask.NO_REPEATING || period == CraftTask.PROCESS_FOR_FUTURE) { + this.wait(timeout); + period = this.getPeriod(); + if (period == CraftTask.NO_REPEATING || period == CraftTask.PROCESS_FOR_FUTURE) { + if (timeout == 0L) { + continue; + } + timeout += timestamp - (timestamp = System.currentTimeMillis()); + if (timeout > 0) { + continue; + } + throw new TimeoutException(); + } + } + if (period == CraftTask.CANCEL) { + throw new CancellationException(); + } + if (period == CraftTask.DONE_FOR_FUTURE) { + if (exception == null) { + return value; + } + throw new ExecutionException(exception); + } + throw new IllegalStateException("Expected " + CraftTask.NO_REPEATING + " to " + CraftTask.DONE_FOR_FUTURE + ", got " + period); + } + } + + @Override + public void run() { + synchronized (this) { + if (getPeriod() == CraftTask.CANCEL) { + return; + } + setPeriod(CraftTask.PROCESS_FOR_FUTURE); + } + try { + value = callable.call(); + } catch (final Exception e) { + exception = e; + } finally { + synchronized (this) { + setPeriod(CraftTask.DONE_FOR_FUTURE); + this.notifyAll(); + } + } + } + + synchronized boolean cancel0() { + if (getPeriod() != CraftTask.NO_REPEATING) { + return false; + } + setPeriod(CraftTask.CANCEL); + notifyAll(); + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java new file mode 100644 index 00000000..6ffc2387 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -0,0 +1,585 @@ +package org.bukkit.craftbukkit.scheduler; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.IllegalPluginAccessException; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.scheduler.BukkitWorker; + +/** + * The fundamental concepts for this implementation: + *
  • Main thread owns {@link #head} and {@link #currentTick}, but it may be read from any thread
  • + *
  • Main thread exclusively controls {@link #temp} and {@link #pending}. + * They are never to be accessed outside of the main thread; alternatives exist to prevent locking.
  • + *
  • {@link #head} to {@link #tail} act as a linked list/queue, with 1 consumer and infinite producers. + * Adding to the tail is atomic and very efficient; utility method is {@link #handle(CraftTask, long)} or {@link #addTask(CraftTask)}.
  • + *
  • Changing the period on a task is delicate. + * Any future task needs to notify waiting threads. + * Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}. + * Another utility method is provided for this, {@link #cancelTask(int)}
  • + *
  • {@link #runners} provides a moderately up-to-date view of active tasks. + * If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.
  • + *
  • Async tasks are responsible for removing themselves from runners
  • + *
  • Sync tasks are only to be removed from runners on the main thread when coupled with a removal from pending and temp.
  • + *
  • Most of the design in this scheduler relies on queuing special tasks to perform any data changes on the main thread. + * When executed from inside a synchronous method, the scheduler will be updated before next execution by virtue of the frequent {@link #parsePending()} calls.
  • + */ +public class CraftScheduler implements BukkitScheduler { + + /** + * Counter for IDs. Order doesn't matter, only uniqueness. + */ + private final AtomicInteger ids = new AtomicInteger(1); + /** + * Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference. + */ + private volatile CraftTask head = new CraftTask(); + /** + * Tail of a linked-list. AtomicReference only matters when adding to queue + */ + private final AtomicReference tail = new AtomicReference(head); + /** + * Main thread logic only + */ + final PriorityQueue pending = new PriorityQueue(10, // Paper + new Comparator() { + public int compare(final CraftTask o1, final CraftTask o2) { + int value = Long.compare(o1.getNextRun(), o2.getNextRun()); + + // If the tasks should run on the same tick they should be run FIFO + return value != 0 ? value : Integer.compare(o1.getTaskId(), o2.getTaskId()); + } + }); + /** + * Main thread logic only + */ + private final List temp = new ArrayList(); + /** + * These are tasks that are currently active. It's provided for 'viewing' the current state. + */ + final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper + /** + * The sync task that is currently running on the main thread. + */ + private volatile CraftTask currentTask = null; + volatile int currentTick = -1; // Paper + //private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot // Paper - moved to AsyncScheduler + //private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; // Paper + //private CraftAsyncDebugger debugTail = debugHead; // Paper + private static final int RECENT_TICKS; + + static { + RECENT_TICKS = 30; + } + + // Paper start + private final CraftScheduler asyncScheduler; + private final boolean isAsyncScheduler; + public CraftScheduler() { + this(false); + } + + public CraftScheduler(boolean isAsync) { + this.isAsyncScheduler = isAsync; + if (isAsync) { + this.asyncScheduler = this; + } else { + this.asyncScheduler = new CraftAsyncScheduler(); + } + } + // Paper end + + @Override + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { + return this.scheduleSyncDelayedTask(plugin, task, 0L); + } + + public BukkitTask runTask(Plugin plugin, Runnable runnable) { + return runTaskLater(plugin, runnable, 0L); + } + + @Deprecated + public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task) { + return this.scheduleAsyncDelayedTask(plugin, task, 0L); + } + + public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable runnable) { + return runTaskLaterAsynchronously(plugin, runnable, 0L); + } + + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) { + return this.scheduleSyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING); + } + + public BukkitTask runTaskLater(Plugin plugin, Runnable runnable, long delay) { + return runTaskTimer(plugin, runnable, delay, CraftTask.NO_REPEATING); + } + + @Deprecated + public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) { + return this.scheduleAsyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING); + } + + public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + return runTaskTimerAsynchronously(plugin, runnable, delay, CraftTask.NO_REPEATING); + } + + public int scheduleSyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) { + return runTaskTimer(plugin, runnable, delay, period).getTaskId(); + } + + public BukkitTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + validate(plugin, runnable); + if (delay < 0L) { + delay = 0; + } + if (period == CraftTask.ERROR) { + period = 1L; + } else if (period < CraftTask.NO_REPEATING) { + period = CraftTask.NO_REPEATING; + } + return handle(new CraftTask(plugin, runnable, nextId(), period), delay); + } + + @Deprecated + public int scheduleAsyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) { + return runTaskTimerAsynchronously(plugin, runnable, delay, period).getTaskId(); + } + + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) { + validate(plugin, runnable); + if (delay < 0L) { + delay = 0; + } + if (period == CraftTask.ERROR) { + period = 1L; + } else if (period < CraftTask.NO_REPEATING) { + period = CraftTask.NO_REPEATING; + } + return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper + } + + public Future callSyncMethod(final Plugin plugin, final Callable task) { + validate(plugin, task); + final CraftFuture future = new CraftFuture(task, plugin, nextId()); + handle(future, 0L); + return future; + } + + public void cancelTask(final int taskId) { + if (taskId <= 0) { + return; + } + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelTask(taskId); + } + // Paper end + CraftTask task = runners.get(taskId); + if (task != null) { + task.cancel0(); + } + task = new CraftTask( + new Runnable() { + @Override + public void run() { + if (!check(CraftScheduler.this.temp)) { + check(CraftScheduler.this.pending); + } + } + private boolean check(final Iterable collection) { + final Iterator tasks = collection.iterator(); + while (tasks.hasNext()) { + final CraftTask task = tasks.next(); + if (task.getTaskId() == taskId) { + task.cancel0(); + tasks.remove(); + if (task.isSync()) { + runners.remove(taskId); + } + return true; + } + } + return false; + }}); // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { + return; + } + if (taskPending.getTaskId() == taskId) { + taskPending.cancel0(); + } + } + } + + public void cancelTasks(final Plugin plugin) { + Validate.notNull(plugin, "Cannot cancel tasks of null plugin"); + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelTasks(plugin); + } + // Paper end + final CraftTask task = new CraftTask( + new Runnable() { + @Override + public void run() { + check(CraftScheduler.this.pending); + check(CraftScheduler.this.temp); + } + void check(final Iterable collection) { + final Iterator tasks = collection.iterator(); + while (tasks.hasNext()) { + final CraftTask task = tasks.next(); + if (task.getOwner().equals(plugin)) { + task.cancel0(); + tasks.remove(); + if (task.isSync()) { + runners.remove(task.getTaskId()); + } + } + } + } + }); // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { + break; + } + if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(plugin)) { + taskPending.cancel0(); + } + } + for (CraftTask runner : runners.values()) { + if (runner.getOwner().equals(plugin)) { + runner.cancel0(); + } + } + } + + public void cancelAllTasks() { + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelAllTasks(); + } + // Paper end + final CraftTask task = new CraftTask( + new Runnable() { + public void run() { + Iterator it = CraftScheduler.this.runners.values().iterator(); + while (it.hasNext()) { + CraftTask task = it.next(); + task.cancel0(); + if (task.isSync()) { + it.remove(); + } + } + CraftScheduler.this.pending.clear(); + CraftScheduler.this.temp.clear(); + } + }); // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { + break; + } + taskPending.cancel0(); + } + for (CraftTask runner : runners.values()) { + runner.cancel0(); + } + } + + public boolean isCurrentlyRunning(final int taskId) { + // Paper start + if (!isAsyncScheduler) { + if (this.asyncScheduler.isCurrentlyRunning(taskId)) { + return true; + } + } + // Paper end + final CraftTask task = runners.get(taskId); + if (task == null) { + return false; + } + if (task.isSync()) { + return (task == currentTask); + } + final CraftAsyncTask asyncTask = (CraftAsyncTask) task; + synchronized (asyncTask.getWorkers()) { + return !asyncTask.getWorkers().isEmpty(); + } + } + + @Override + public boolean isQueued(final int taskId) { + if (taskId <= 0) { + return false; + } + // Paper start + if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) { + return true; + } + // Paper end + for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() == taskId) { + return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run + } + } + CraftTask task = runners.get(taskId); + return task != null && task.getPeriod() >= CraftTask.NO_REPEATING; + } + + @Override + public List getActiveWorkers() { + // Paper start + if (!isAsyncScheduler) { + //noinspection TailRecursion + return this.asyncScheduler.getActiveWorkers(); + } + // Paper end + final ArrayList workers = new ArrayList(); + for (final CraftTask taskObj : runners.values()) { + // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread + if (taskObj.isSync()) { + continue; + } + final CraftAsyncTask task = (CraftAsyncTask) taskObj; + synchronized (task.getWorkers()) { + // This will never have an issue with stale threads; it's state-safe + workers.addAll(task.getWorkers()); + } + } + return workers; + } + + public List getPendingTasks() { + final ArrayList truePending = new ArrayList(); + for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() != -1) { + // -1 is special code + truePending.add(task); + } + } + + final ArrayList pending = new ArrayList(); + for (CraftTask task : runners.values()) { + if (task.getPeriod() >= CraftTask.NO_REPEATING) { + pending.add(task); + } + } + + for (final CraftTask task : truePending) { + if (task.getPeriod() >= CraftTask.NO_REPEATING && !pending.contains(task)) { + pending.add(task); + } + } + // Paper start + if (!this.isAsyncScheduler) { + pending.addAll(this.asyncScheduler.getPendingTasks()); + } + // Paper end + return pending; + } + + /** + * This method is designed to never block or wait for locks; an immediate execution of all current tasks. + */ + public void mainThreadHeartbeat(final int currentTick) { + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.mainThreadHeartbeat(currentTick); + } + // Paper end + this.currentTick = currentTick; + final List temp = this.temp; + parsePending(); + while (isReady(currentTick)) { + final CraftTask task = pending.remove(); + if (task.getPeriod() < CraftTask.NO_REPEATING) { + if (task.isSync()) { + runners.remove(task.getTaskId(), task); + } + parsePending(); + continue; + } + if (task.isSync()) { + currentTask = task; + try { + task.run(); + } catch (final Throwable throwable) { + // Paper start + String msg = String.format( + "Task #%s for %s generated an exception", + task.getTaskId(), + task.getOwner().getDescription().getFullName()); + task.getOwner().getLogger().log( + Level.WARNING, + msg, + throwable); + //task.getOwner().getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))); + // Paper end + } finally { + currentTask = null; + } + parsePending(); + } else { + //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper + task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } + final long period = task.getPeriod(); // State consistency + if (period > 0) { + task.setNextRun(currentTick + period); + temp.add(task); + } else if (task.isSync()) { + runners.remove(task.getTaskId()); + } + } + pending.addAll(temp); + temp.clear(); + //debugHead = debugHead.getNextHead(currentTick); // Paper + } + + protected void addTask(final CraftTask task) { + final AtomicReference tail = this.tail; + CraftTask tailTask = tail.get(); + while (!tail.compareAndSet(tailTask, task)) { + tailTask = tail.get(); + } + tailTask.setNext(task); + } + + protected CraftTask handle(final CraftTask task, final long delay) { // Paper + // Paper start + if (!this.isAsyncScheduler && !task.isSync()) { + this.asyncScheduler.handle(task, delay); + return task; + } + // Paper end + task.setNextRun(currentTick + delay); + addTask(task); + return task; + } + + private static void validate(final Plugin plugin, final Object task) { + Validate.notNull(plugin, "Plugin cannot be null"); + Validate.notNull(task, "Task cannot be null"); + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); + } + } + + private int nextId() { + return ids.incrementAndGet(); + } + + void parsePending() { // Paper + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; + for (; task != null; task = (lastTask = task).getNext()) { + if (task.getTaskId() == -1) { + task.run(); + } else if (task.getPeriod() >= CraftTask.NO_REPEATING) { + pending.add(task); + runners.put(task.getTaskId(), task); + } + } + // We split this because of the way things are ordered for all of the async calls in CraftScheduler + // (it prevents race-conditions) + for (task = head; task != lastTask; task = head) { + head = task.getNext(); + task.setNext(null); + } + this.head = lastTask; + } + + private boolean isReady(final int currentTick) { + return !pending.isEmpty() && pending.peek().getNextRun() <= currentTick; + } + + @Override + public String toString() { + // Paper start + return ""; + /* + int debugTick = currentTick; + StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{'); + debugHead.debugTo(string); + return string.append('}').toString(); + */ + // Paper end + } + + @Deprecated + @Override + public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay) { + return scheduleSyncDelayedTask(plugin, (Runnable) task, delay); + } + + @Deprecated + @Override + public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task) { + return scheduleSyncDelayedTask(plugin, (Runnable) task); + } + + @Deprecated + @Override + public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period) { + return scheduleSyncRepeatingTask(plugin, (Runnable) task, delay, period); + } + + @Deprecated + @Override + public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException { + return runTask(plugin, (Runnable) task); + } + + @Deprecated + @Override + public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException { + return runTaskAsynchronously(plugin, (Runnable) task); + } + + @Deprecated + @Override + public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException { + return runTaskLater(plugin, (Runnable) task, delay); + } + + @Deprecated + @Override + public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException { + return runTaskLaterAsynchronously(plugin, (Runnable) task, delay); + } + + @Deprecated + @Override + public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + return runTaskTimer(plugin, (Runnable) task, delay, period); + } + + @Deprecated + @Override + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + return runTaskTimerAsynchronously(plugin, (Runnable) task, delay, period); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java new file mode 100644 index 00000000..bffaee38 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -0,0 +1,130 @@ +package org.bukkit.craftbukkit.scheduler; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + + +public class CraftTask implements BukkitTask, Runnable { + + private volatile CraftTask next = null; + public static final int ERROR = 0; + public static final int NO_REPEATING = -1; + public static final int CANCEL = -2; + public static final int PROCESS_FOR_FUTURE = -3; + public static final int DONE_FOR_FUTURE = -4; + /** + * -1 means no repeating
    + * -2 means cancel
    + * -3 means processing for Future
    + * -4 means done for Future
    + * Never 0
    + * >0 means number of ticks to wait between each execution + */ + private volatile long period; + private long nextRun; + public final Runnable task; + private final Plugin plugin; + private final int id; + + CraftTask() { + this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } + + CraftTask(final Runnable task) { + this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } + + // Spigot start + public String timingName = null; + CraftTask(String timingName) { + this(timingName, null, null, -1, -1); + } + CraftTask(String timingName, final Runnable task) { + this(timingName, null, task, -1, -1); + } + CraftTask(String timingName, final Plugin plugin, final Runnable task, final int id, final long period) { + this.plugin = plugin; + this.task = task; + this.id = id; + this.period = period; + this.timingName = timingName == null && task == null ? "Unknown" : timingName; + } + + CraftTask(final Plugin plugin, final Runnable task, final int id, final long period) { + this(null, plugin, task, id, period); + // Spigot end + } + + public final int getTaskId() { + return id; + } + + public final Plugin getOwner() { + return plugin; + } + + public boolean isSync() { + return true; + } + + public void run() { + task.run(); + } + + long getPeriod() { + return period; + } + + void setPeriod(long period) { + this.period = period; + } + + long getNextRun() { + return nextRun; + } + + void setNextRun(long nextRun) { + this.nextRun = nextRun; + } + + CraftTask getNext() { + return next; + } + + void setNext(CraftTask next) { + this.next = next; + } + + Class getTaskClass() { + return task.getClass(); + } + + @Override + public boolean isCancelled() { + return (period == CraftTask.CANCEL); + } + + public void cancel() { + Bukkit.getScheduler().cancelTask(id); + } + + /** + * This method properly sets the status to cancelled, synchronizing when required. + * + * @return false if it is a craft future task that has already begun execution, true otherwise + */ + boolean cancel0() { + setPeriod(CraftTask.CANCEL); + return true; + } + + // Spigot start + public String getTaskName() { + if (timingName != null) { + return timingName; + } + return task.getClass().getName(); + } + // Spigot end +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java new file mode 100644 index 00000000..e494a042 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java @@ -0,0 +1,64 @@ +package org.bukkit.craftbukkit.scoreboard; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.scoreboard.IScoreCriteria; +import net.minecraft.scoreboard.ScoreObjective; + +final class CraftCriteria { + static final Map DEFAULTS; + static final CraftCriteria DUMMY; + + static { + ImmutableMap.Builder defaults = ImmutableMap.builder(); + + for (Map.Entry entry : ((Map ) IScoreCriteria.INSTANCES).entrySet()) { + String name = entry.getKey().toString(); + IScoreCriteria criteria = (IScoreCriteria) entry.getValue(); + + defaults.put(name, new CraftCriteria(criteria)); + } + + DEFAULTS = defaults.build(); + DUMMY = DEFAULTS.get("dummy"); + } + + final IScoreCriteria criteria; + final String bukkitName; + + private CraftCriteria(String bukkitName) { + this.bukkitName = bukkitName; + this.criteria = DUMMY.criteria; + } + + private CraftCriteria(IScoreCriteria criteria) { + this.criteria = criteria; + this.bukkitName = criteria.getName(); + } + + static CraftCriteria getFromNMS(ScoreObjective objective) { + return DEFAULTS.get(objective.getCriteria().getName()); + } + + static CraftCriteria getFromBukkit(String name) { + final CraftCriteria criteria = DEFAULTS.get(name); + if (criteria != null) { + return criteria; + } + return new CraftCriteria(name); + } + + @Override + public boolean equals(Object that) { + if (!(that instanceof CraftCriteria)) { + return false; + } + return ((CraftCriteria) that).bukkitName.equals(this.bukkitName); + } + + @Override + public int hashCode() { + return this.bukkitName.hashCode() ^ CraftCriteria.class.hashCode(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java new file mode 100644 index 00000000..8936d4e7 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java @@ -0,0 +1,136 @@ +package org.bukkit.craftbukkit.scoreboard; + +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.Scoreboard; +import org.apache.commons.lang3.Validate; +import org.bukkit.OfflinePlayer; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; + +final class CraftObjective extends CraftScoreboardComponent implements Objective { + private final ScoreObjective objective; + private final CraftCriteria criteria; + + CraftObjective(CraftScoreboard scoreboard, ScoreObjective objective) { + super(scoreboard); + this.objective = objective; + this.criteria = CraftCriteria.getFromNMS(objective); + } + + ScoreObjective getHandle() { + return objective; + } + + public String getName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return objective.getName(); + } + + public String getDisplayName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return objective.getDisplayName(); + } + + public void setDisplayName(String displayName) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(displayName, "Display name cannot be null"); + Validate.isTrue(displayName.length() <= 32, "Display name '" + displayName + "' is longer than the limit of 32 characters"); + CraftScoreboard scoreboard = checkState(); + + objective.setDisplayName(displayName); + } + + public String getCriteria() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return criteria.bukkitName; + } + + public boolean isModifiable() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return !criteria.criteria.isReadOnly(); + } + + public void setDisplaySlot(DisplaySlot slot) throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + Scoreboard board = scoreboard.board; + ScoreObjective objective = this.objective; + + for (int i = 0; i < CraftScoreboardTranslations.MAX_DISPLAY_SLOT; i++) { + if (board.getObjectiveInDisplaySlot(i) == objective) { + board.setObjectiveInDisplaySlot(i, null); + } + } + if (slot != null) { + int slotNumber = CraftScoreboardTranslations.fromBukkitSlot(slot); + board.setObjectiveInDisplaySlot(slotNumber, getHandle()); + } + } + + public DisplaySlot getDisplaySlot() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + Scoreboard board = scoreboard.board; + ScoreObjective objective = this.objective; + + for (int i = 0; i < CraftScoreboardTranslations.MAX_DISPLAY_SLOT; i++) { + if (board.getObjectiveInDisplaySlot(i) == objective) { + return CraftScoreboardTranslations.toBukkitSlot(i); + } + } + return null; + } + + public Score getScore(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { + Validate.notNull(player, "Player cannot be null"); + CraftScoreboard scoreboard = checkState(); + + return new CraftScore(this, player.getName()); + } + + public Score getScore(String entry) throws IllegalArgumentException, IllegalStateException { + Validate.notNull(entry, "Entry cannot be null"); + CraftScoreboard scoreboard = checkState(); + + return new CraftScore(this, entry); + } + + @Override + public void unregister() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + scoreboard.board.removeObjective(objective); + } + + @Override + CraftScoreboard checkState() throws IllegalStateException { + if (getScoreboard().board.getObjective(objective.getName()) == null) { + throw new IllegalStateException("Unregistered scoreboard component"); + } + + return getScoreboard(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + (this.objective != null ? this.objective.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CraftObjective other = (CraftObjective) obj; + return !(this.objective != other.objective && (this.objective == null || !this.objective.equals(other.objective))); + } + + +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java new file mode 100644 index 00000000..ab9db245 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java @@ -0,0 +1,67 @@ +package org.bukkit.craftbukkit.scoreboard; + +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.Scoreboard; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; + +import java.util.Map; + +/** + * TL;DR: This class is special and lazily grabs a handle... + * ...because a handle is a full fledged (I think permanent) hashMap for the associated name. + *

    + * Also, as an added perk, a CraftScore will (intentionally) stay a valid reference so long as objective is valid. + */ +final class CraftScore implements Score { + private final String entry; + private final CraftObjective objective; + + CraftScore(CraftObjective objective, String entry) { + this.objective = objective; + this.entry = entry; + } + + public OfflinePlayer getPlayer() { + return Bukkit.getOfflinePlayer(entry); + } + + public String getEntry() { + return entry; + } + + public Objective getObjective() { + return objective; + } + + public int getScore() throws IllegalStateException { + Scoreboard board = objective.checkState().board; + + if (board.getObjectiveNames().contains(entry)) { // Lazy + Map scores = board.getObjectivesForEntity(entry); + net.minecraft.scoreboard.Score score = scores.get(objective.getHandle()); + if (score != null) { // Lazy + return score.getScorePoints(); + } + } + + return 0; // Lazy + } + + public void setScore(int score) throws IllegalStateException { + objective.checkState().board.getOrCreateScore(entry, objective.getHandle()).setScorePoints(score); + } + + @Override + public boolean isScoreSet() throws IllegalStateException { + Scoreboard board = objective.checkState().board; + + return board.getObjectiveNames().contains(entry) && board.getObjectivesForEntity(entry).containsKey(objective.getHandle()); + } + + public CraftScoreboard getScoreboard() { + return objective.getScoreboard(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java new file mode 100644 index 00000000..508628e5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java @@ -0,0 +1,170 @@ +package org.bukkit.craftbukkit.scoreboard; + +import com.google.common.base.Function; +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.ScorePlayerTeam; +import net.minecraft.scoreboard.Scoreboard; +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Team; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +import java.util.Collection; + +public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + final Scoreboard board; + + CraftScoreboard(Scoreboard board) { + this.board = board; + } + + public CraftObjective registerNewObjective(String name, String criteria) throws IllegalArgumentException { + Validate.notNull(name, "Objective name cannot be null"); + Validate.notNull(criteria, "Criteria cannot be null"); + Validate.isTrue(name.length() <= 16, "The name '" + name + "' is longer than the limit of 16 characters"); + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); + ScoreObjective objective = board.addScoreObjective(name, craftCriteria.criteria); + return new CraftObjective(this, objective); + } + + public Objective getObjective(String name) throws IllegalArgumentException { + Validate.notNull(name, "Name cannot be null"); + ScoreObjective nms = board.getObjective(name); + return nms == null ? null : new CraftObjective(this, nms); + } + + public ImmutableSet getObjectivesByCriteria(String criteria) throws IllegalArgumentException { + Validate.notNull(criteria, "Criteria cannot be null"); + + ImmutableSet.Builder objectives = ImmutableSet.builder(); + for (ScoreObjective netObjective : (Collection) this.board.getScoreObjectives()) { + CraftObjective objective = new CraftObjective(this, netObjective); + if (objective.getCriteria().equals(criteria)) { + objectives.add(objective); + } + } + return objectives.build(); + } + + public ImmutableSet getObjectives() { + return ImmutableSet.copyOf(Iterables.transform((Collection) this.board.getScoreObjectives(), new Function() { + + @Override + public Objective apply(ScoreObjective input) { + return new CraftObjective(CraftScoreboard.this, input); + } + })); + } + + public Objective getObjective(DisplaySlot slot) throws IllegalArgumentException { + Validate.notNull(slot, "Display slot cannot be null"); + ScoreObjective objective = board.getObjectiveInDisplaySlot(CraftScoreboardTranslations.fromBukkitSlot(slot)); + if (objective == null) { + return null; + } + return new CraftObjective(this, objective); + } + + public ImmutableSet getScores(OfflinePlayer player) throws IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + + return getScores(player.getName()); + } + + public ImmutableSet getScores(String entry) throws IllegalArgumentException { + Validate.notNull(entry, "Entry cannot be null"); + + ImmutableSet.Builder scores = ImmutableSet.builder(); + for (ScoreObjective objective : (Collection) this.board.getScoreObjectives()) { + scores.add(new CraftScore(new CraftObjective(this, objective), entry)); + } + return scores.build(); + } + + public void resetScores(OfflinePlayer player) throws IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + + resetScores(player.getName()); + } + + public void resetScores(String entry) throws IllegalArgumentException { + Validate.notNull(entry, "Entry cannot be null"); + + for (ScoreObjective objective : (Collection) this.board.getScoreObjectives()) { + board.removeObjectiveFromEntity(entry, objective); + } + } + + public Team getPlayerTeam(OfflinePlayer player) throws IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + + ScorePlayerTeam team = board.getPlayersTeam(player.getName()); + return team == null ? null : new CraftTeam(this, team); + } + + public Team getEntryTeam(String entry) throws IllegalArgumentException { + Validate.notNull(entry, "Entry cannot be null"); + + ScorePlayerTeam team = board.getPlayersTeam(entry); + return team == null ? null : new CraftTeam(this, team); + } + + public Team getTeam(String teamName) throws IllegalArgumentException { + Validate.notNull(teamName, "Team name cannot be null"); + + ScorePlayerTeam team = board.getTeam(teamName); + return team == null ? null : new CraftTeam(this, team); + } + + public ImmutableSet getTeams() { + return ImmutableSet.copyOf(Iterables.transform((Collection) this.board.getTeams(), new Function() { + + @Override + public Team apply(ScorePlayerTeam input) { + return new CraftTeam(CraftScoreboard.this, input); + } + })); + } + + public Team registerNewTeam(String name) throws IllegalArgumentException { + Validate.notNull(name, "Team name cannot be null"); + Validate.isTrue(name.length() <= 16, "Team name '" + name + "' is longer than the limit of 16 characters"); + Validate.isTrue(board.getTeam(name) == null, "Team name '" + name + "' is already in use"); + + return new CraftTeam(this, board.createTeam(name)); + } + + public ImmutableSet getPlayers() { + ImmutableSet.Builder players = ImmutableSet.builder(); + for (Object playerName : board.getObjectiveNames()) { + players.add(Bukkit.getOfflinePlayer(playerName.toString())); + } + return players.build(); + } + + public ImmutableSet getEntries() { + ImmutableSet.Builder entries = ImmutableSet.builder(); + for (Object entry : board.getObjectiveNames()) { + entries.add(entry.toString()); + } + return entries.build(); + } + + public void clearSlot(DisplaySlot slot) throws IllegalArgumentException { + Validate.notNull(slot, "Slot cannot be null"); + board.setObjectiveInDisplaySlot(CraftScoreboardTranslations.fromBukkitSlot(slot), null); + } + + // CraftBukkit method + public Scoreboard getHandle() { + return board; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardComponent.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardComponent.java new file mode 100644 index 00000000..d26d09d4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardComponent.java @@ -0,0 +1,17 @@ +package org.bukkit.craftbukkit.scoreboard; + +abstract class CraftScoreboardComponent { + private CraftScoreboard scoreboard; + + CraftScoreboardComponent(CraftScoreboard scoreboard) { + this.scoreboard = scoreboard; + } + + abstract CraftScoreboard checkState() throws IllegalStateException; + + public CraftScoreboard getScoreboard() { + return scoreboard; + } + + abstract void unregister() throws IllegalStateException; +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java new file mode 100644 index 00000000..b3164dc4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java @@ -0,0 +1,109 @@ +package org.bukkit.craftbukkit.scoreboard; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.SPacketScoreboardObjective; +import net.minecraft.network.play.server.SPacketTeams; +import net.minecraft.scoreboard.IScoreCriteria; +import net.minecraft.scoreboard.Score; +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.ScorePlayerTeam; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.scoreboard.ServerScoreboard; +import net.minecraft.server.MinecraftServer; + +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.WeakCollection; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.ScoreboardManager; + +public final class CraftScoreboardManager implements ScoreboardManager { + private final CraftScoreboard mainScoreboard; + private final MinecraftServer server; + private final Collection scoreboards = new WeakCollection(); + private final Map playerBoards = new HashMap(); + + public CraftScoreboardManager(MinecraftServer minecraftserver, Scoreboard scoreboardServer) { + mainScoreboard = new CraftScoreboard(scoreboardServer); + server = minecraftserver; + scoreboards.add(mainScoreboard); + } + + public CraftScoreboard getMainScoreboard() { + return mainScoreboard; + } + + public CraftScoreboard getNewScoreboard() { + CraftScoreboard scoreboard = new CraftScoreboard(new ServerScoreboard(server)); + scoreboards.add(scoreboard); + return scoreboard; + } + + // CraftBukkit method + public CraftScoreboard getPlayerBoard(CraftPlayer player) { + CraftScoreboard board = playerBoards.get(player); + return (CraftScoreboard) (board == null ? getMainScoreboard() : board); + } + + // CraftBukkit method + public void setPlayerBoard(CraftPlayer player, org.bukkit.scoreboard.Scoreboard bukkitScoreboard) throws IllegalArgumentException { + Validate.isTrue(bukkitScoreboard instanceof CraftScoreboard, "Cannot set player scoreboard to an unregistered Scoreboard"); + + CraftScoreboard scoreboard = (CraftScoreboard) bukkitScoreboard; + Scoreboard oldboard = getPlayerBoard(player).getHandle(); + Scoreboard newboard = scoreboard.getHandle(); + EntityPlayerMP entityplayer = player.getHandle(); + + if (oldboard == newboard) { + return; + } + + if (scoreboard == mainScoreboard) { + playerBoards.remove(player); + } else { + playerBoards.put(player, (CraftScoreboard) scoreboard); + } + + // Old objective tracking + HashSet removed = new HashSet(); + for (int i = 0; i < 3; ++i) { + ScoreObjective scoreboardobjective = oldboard.getObjectiveInDisplaySlot(i); + if (scoreboardobjective != null && !removed.contains(scoreboardobjective)) { + entityplayer.connection.sendPacket(new SPacketScoreboardObjective(scoreboardobjective, 1)); + removed.add(scoreboardobjective); + } + } + + // Old team tracking + Iterator iterator = oldboard.getTeams().iterator(); + while (iterator.hasNext()) { + ScorePlayerTeam scoreboardteam = (ScorePlayerTeam) iterator.next(); + entityplayer.connection.sendPacket(new SPacketTeams(scoreboardteam, 1)); + } + + // The above is the reverse of the below method. + server.getPlayerList().sendScoreboard((ServerScoreboard) newboard, player.getHandle()); + } + + // CraftBukkit method + public void removePlayer(Player player) { + playerBoards.remove(player); + } + + // CraftBukkit method + public Collection getScoreboardScores(IScoreCriteria criteria, String name, Collection collection) { + for (CraftScoreboard scoreboard : scoreboards) { + Scoreboard board = scoreboard.board; + for (ScoreObjective objective : (Iterable) board.getObjectivesFromCriteria(criteria)) { + collection.add(board.getOrCreateScore(name, objective)); + } + } + return collection; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java new file mode 100644 index 00000000..35db2f94 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java @@ -0,0 +1,25 @@ +package org.bukkit.craftbukkit.scoreboard; + +import net.minecraft.scoreboard.Scoreboard; +import org.bukkit.scoreboard.DisplaySlot; + +import com.google.common.collect.ImmutableBiMap; + +class CraftScoreboardTranslations { + static final int MAX_DISPLAY_SLOT = 3; + static ImmutableBiMap SLOTS = ImmutableBiMap.of( + DisplaySlot.BELOW_NAME, "belowName", + DisplaySlot.PLAYER_LIST, "list", + DisplaySlot.SIDEBAR, "sidebar"); + + private CraftScoreboardTranslations() {} + + static DisplaySlot toBukkitSlot(int i) { + return SLOTS.inverse().get(Scoreboard.getObjectiveDisplaySlot(i)); + } + + static int fromBukkitSlot(DisplaySlot slot) { + return Scoreboard.getObjectiveDisplaySlotNumber(SLOTS.get(slot)); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java new file mode 100644 index 00000000..6bf2f75b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java @@ -0,0 +1,295 @@ +package org.bukkit.craftbukkit.scoreboard; + +import java.util.Set; + +import net.minecraft.scoreboard.ScorePlayerTeam; +import net.minecraft.scoreboard.Team.EnumVisible; +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.scoreboard.NameTagVisibility; +import org.bukkit.scoreboard.Team; + +import com.google.common.collect.ImmutableSet; + +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +final class CraftTeam extends CraftScoreboardComponent implements Team { + private final ScorePlayerTeam team; + + CraftTeam(CraftScoreboard scoreboard, ScorePlayerTeam team) { + super(scoreboard); + this.team = team; + } + + public String getName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getName(); + } + + public String getDisplayName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getDisplayName(); + } + + public void setDisplayName(String displayName) throws IllegalStateException { + Validate.notNull(displayName, "Display name cannot be null"); + Validate.isTrue(displayName.length() <= 32, "Display name '" + displayName + "' is longer than the limit of 32 characters"); + CraftScoreboard scoreboard = checkState(); + + team.setDisplayName(displayName); + } + + public String getPrefix() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getPrefix(); + } + + public void setPrefix(String prefix) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(prefix, "Prefix cannot be null"); + Validate.isTrue(prefix.length() <= 16, "Prefix '" + prefix + "' is longer than the limit of 16 characters"); + CraftScoreboard scoreboard = checkState(); + + team.setPrefix(prefix); + } + + public String getSuffix() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getSuffix(); + } + + public void setSuffix(String suffix) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(suffix, "Suffix cannot be null"); + Validate.isTrue(suffix.length() <= 16, "Suffix '" + suffix + "' is longer than the limit of 16 characters"); + CraftScoreboard scoreboard = checkState(); + + team.setSuffix(suffix); + } + + @Override + public ChatColor getColor() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return CraftChatMessage.getColor(team.getColor()); + } + + @Override + public void setColor(ChatColor color) { + Validate.notNull(color, "Color cannot be null"); + CraftScoreboard scoreboard = checkState(); + + team.setColor(CraftChatMessage.getColor(color)); + scoreboard.board.broadcastTeamInfoUpdate(team); // SPIGOT-3684 - backing team fires this for prefix/suffix but not colour + } + + public boolean allowFriendlyFire() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getAllowFriendlyFire(); + } + + public void setAllowFriendlyFire(boolean enabled) throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + team.setAllowFriendlyFire(enabled); + } + + public boolean canSeeFriendlyInvisibles() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getSeeFriendlyInvisiblesEnabled(); + } + + public void setCanSeeFriendlyInvisibles(boolean enabled) throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + team.setSeeFriendlyInvisiblesEnabled(enabled); + } + + public NameTagVisibility getNameTagVisibility() throws IllegalArgumentException { + CraftScoreboard scoreboard = checkState(); + + return notchToBukkit(team.getNameTagVisibility()); + } + + public void setNameTagVisibility(NameTagVisibility visibility) throws IllegalArgumentException { + CraftScoreboard scoreboard = checkState(); + + team.setNameTagVisibility(bukkitToNotch(visibility)); + } + + public Set getPlayers() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + ImmutableSet.Builder players = ImmutableSet.builder(); + for (String playerName : team.getMembershipCollection()) { + players.add(Bukkit.getOfflinePlayer(playerName)); + } + return players.build(); + } + + @Override + public Set getEntries() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + ImmutableSet.Builder entries = ImmutableSet.builder(); + for (String playerName: team.getMembershipCollection()){ + entries.add(playerName); + } + return entries.build(); + } + + public int getSize() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + return team.getMembershipCollection().size(); + } + + public void addPlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + addEntry(player.getName()); + } + + public void addEntry(String entry) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(entry, "Entry cannot be null"); + CraftScoreboard scoreboard = checkState(); + + scoreboard.board.addPlayerToTeam(entry, team.getName()); + } + + public boolean removePlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + return removeEntry(player.getName()); + } + + public boolean removeEntry(String entry) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(entry, "Entry cannot be null"); + CraftScoreboard scoreboard = checkState(); + + if (!team.getMembershipCollection().contains(entry)) { + return false; + } + + scoreboard.board.removePlayerFromTeam(entry, team); + return true; + } + + public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { + Validate.notNull(player, "OfflinePlayer cannot be null"); + return hasEntry(player.getName()); + } + + public boolean hasEntry(String entry) throws IllegalArgumentException, IllegalStateException { + Validate.notNull("Entry cannot be null"); + + CraftScoreboard scoreboard = checkState(); + + return team.getMembershipCollection().contains(entry); + } + + @Override + public void unregister() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); + + scoreboard.board.removeTeam(team); + } + + @Override + public OptionStatus getOption(Option option) throws IllegalStateException { + checkState(); + + switch (option) { + case NAME_TAG_VISIBILITY: + return OptionStatus.values()[team.getNameTagVisibility().ordinal()]; + case DEATH_MESSAGE_VISIBILITY: + return OptionStatus.values()[team.getDeathMessageVisibility().ordinal()]; + case COLLISION_RULE: + return OptionStatus.values()[team.getCollisionRule().ordinal()]; + default: + throw new IllegalArgumentException("Unrecognised option " + option); + } + } + + @Override + public void setOption(Option option, OptionStatus status) throws IllegalStateException { + checkState(); + + switch (option) { + case NAME_TAG_VISIBILITY: + team.setNameTagVisibility(EnumVisible.values()[status.ordinal()]); + break; + case DEATH_MESSAGE_VISIBILITY: + team.setDeathMessageVisibility(EnumVisible.values()[status.ordinal()]); + break; + case COLLISION_RULE: + team.setCollisionRule(net.minecraft.scoreboard.Team.CollisionRule.values()[status.ordinal()]); + break; + default: + throw new IllegalArgumentException("Unrecognised option " + option); + } + } + + public static EnumVisible bukkitToNotch(NameTagVisibility visibility) { + switch (visibility) { + case ALWAYS: + return EnumVisible.ALWAYS; + case NEVER: + return EnumVisible.NEVER; + case HIDE_FOR_OTHER_TEAMS: + return EnumVisible.HIDE_FOR_OTHER_TEAMS; + case HIDE_FOR_OWN_TEAM: + return EnumVisible.HIDE_FOR_OWN_TEAM; + default: + throw new IllegalArgumentException("Unknown visibility level " + visibility); + } + } + + public static NameTagVisibility notchToBukkit(EnumVisible visibility) { + switch (visibility) { + case ALWAYS: + return NameTagVisibility.ALWAYS; + case NEVER: + return NameTagVisibility.NEVER; + case HIDE_FOR_OTHER_TEAMS: + return NameTagVisibility.HIDE_FOR_OTHER_TEAMS; + case HIDE_FOR_OWN_TEAM: + return NameTagVisibility.HIDE_FOR_OWN_TEAM; + default: + throw new IllegalArgumentException("Unknown visibility level " + visibility); + } + } + + @Override + CraftScoreboard checkState() throws IllegalStateException { + if (getScoreboard().board.getTeam(team.getName()) == null) { + throw new IllegalStateException("Unregistered scoreboard component"); + } + + return getScoreboard(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 43 * hash + (this.team != null ? this.team.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CraftTeam other = (CraftTeam) obj; + return !(this.team != other.team && (this.team == null || !this.team.equals(other.team))); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java new file mode 100644 index 00000000..7994c1b2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java @@ -0,0 +1,354 @@ +package org.bukkit.craftbukkit.util; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import org.apache.commons.lang3.Validate; + +/** + * Executes tasks using a multi-stage process executor. Synchronous executions are via {@link AsynchronousExecutor#finishActive()} or the {@link AsynchronousExecutor#get(Object)} methods. + *

  • Stage 1 creates the object from a parameter, and is usually called asynchronously. + *
  • Stage 2 takes the parameter and object from stage 1 and does any synchronous processing to prepare it. + *
  • Stage 3 takes the parameter and object from stage 1, as well as a callback that was registered, and performs any synchronous calculations. + * + * @param

    The type of parameter you provide to make the object that will be created. It should implement {@link Object#hashCode()} and {@link Object#equals(Object)} if you want to get the value early. + * @param The type of object you provide. This is created in stage 1, and passed to stage 2, 3, and returned if get() is called. + * @param The type of callback you provide. You may register many of these to be passed to the provider in stage 3, one at a time. + * @param A type of exception you may throw and expect to be handled by the main thread + * @author Wesley Wolfe (c) 2012, 2014 + */ +public final class AsynchronousExecutor { + + public interface CallBackProvider extends ThreadFactory { + + /** + * Normally an asynchronous call, but can be synchronous + * + * @param parameter parameter object provided + * @return the created object + */ + T callStage1(P parameter) throws E; + + /** + * Synchronous call + * + * @param parameter parameter object provided + * @param object the previously created object + */ + void callStage2(P parameter, T object) throws E; + + /** + * Synchronous call, called multiple times, once per registered callback + * + * @param parameter parameter object provided + * @param object the previously created object + * @param callback the current callback to execute + */ + void callStage3(P parameter, T object, C callback) throws E; + } + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE_FIELD = AtomicIntegerFieldUpdater.newUpdater(AsynchronousExecutor.Task.class, "state"); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static boolean set(AsynchronousExecutor.Task $this, int expected, int value) { + return STATE_FIELD.compareAndSet($this, expected, value); + } + + class Task implements Runnable { + static final int PENDING = 0x0; + static final int STAGE_1_ASYNC = PENDING + 1; + static final int STAGE_1_SYNC = STAGE_1_ASYNC + 1; + static final int STAGE_1_COMPLETE = STAGE_1_SYNC + 1; + static final int FINISHED = STAGE_1_COMPLETE + 1; + + volatile int state = PENDING; + final P parameter; + T object; + final List callbacks = new LinkedList(); + E t = null; + + Task(final P parameter) { + this.parameter = parameter; + } + + public void run() { + if (initAsync()) { + finished.add(this); + } + } + + boolean initAsync() { + if (set(this, PENDING, STAGE_1_ASYNC)) { + boolean ret = true; + + try { + init(); + } finally { + if (set(this, STAGE_1_ASYNC, STAGE_1_COMPLETE)) { + // No one is/will be waiting + } else { + // We know that the sync thread will be waiting + synchronized (this) { + if (state != STAGE_1_SYNC) { + // They beat us to the synchronized block + this.notifyAll(); + } else { + // We beat them to the synchronized block + } + state = STAGE_1_COMPLETE; // They're already synchronized, atomic locks are not needed + } + // We want to return false, because we know a synchronous task already handled the finish() + ret = false; // Don't return inside finally; VERY bad practice. + } + } + + return ret; + } else { + return false; + } + } + + void initSync() { + if (set(this, PENDING, STAGE_1_COMPLETE)) { + // If we succeed that variable switch, good as done + init(); + } else if (set(this, STAGE_1_ASYNC, STAGE_1_SYNC)) { + // Async thread is running, but this shouldn't be likely; we need to sync to wait on them because of it. + synchronized (this) { + if (set(this, STAGE_1_SYNC, PENDING)) { // They might NOT synchronized yet, atomic lock IS needed + // We are the first into the lock + while (state != STAGE_1_COMPLETE) { + try { + this.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Unable to handle interruption on " + parameter, e); + } + } + } else { + // They beat us to the synchronized block + } + } + } else { + // Async thread is not pending, the more likely situation for a task not pending + } + } + + @SuppressWarnings("unchecked") + void init() { + try { + object = provider.callStage1(parameter); + } catch (final Throwable t) { + this.t = (E) t; + } + } + + @SuppressWarnings("unchecked") + T get() throws E { + initSync(); + if (callbacks.isEmpty()) { + // 'this' is a placeholder to prevent callbacks from being empty during finish call + // See get method below + callbacks.add((C) this); + } + finish(); + return object; + } + + void finish() throws E { + switch (state) { + default: + case PENDING: + case STAGE_1_ASYNC: + case STAGE_1_SYNC: + throw new IllegalStateException("Attempting to finish unprepared(" + state + ") task(" + parameter + ")"); + case STAGE_1_COMPLETE: + try { + if (t != null) { + throw t; + } + if (callbacks.isEmpty()) { + return; + } + + final CallBackProvider provider = AsynchronousExecutor.this.provider; + final P parameter = this.parameter; + final T object = this.object; + + provider.callStage2(parameter, object); + for (C callback : callbacks) { + if (callback == this) { + // 'this' is a placeholder to prevent callbacks from being empty on a get() call + // See get method above + continue; + } + provider.callStage3(parameter, object, callback); + } + } finally { + tasks.remove(parameter); + state = FINISHED; + } + case FINISHED: + } + } + + boolean drop() { + if (set(this, PENDING, FINISHED)) { + // If we succeed that variable switch, good as forgotten + tasks.remove(parameter); + return true; + } else { + // We need the async thread to finish normally to properly dispose of the task + return false; + } + } + } + + final CallBackProvider provider; + final Queue finished = new ConcurrentLinkedQueue(); + final Map tasks = new HashMap(); + final ThreadPoolExecutor pool; + + /** + * Uses a thread pool to pass executions to the provider. + * @see AsynchronousExecutor + */ + public AsynchronousExecutor(final CallBackProvider provider, final int coreSize) { + Validate.notNull(provider, "Provider cannot be null"); + this.provider = provider; + + // We have an unbound queue size so do not need a max thread size + pool = new ThreadPoolExecutor(coreSize, Integer.MAX_VALUE, 60l, TimeUnit.SECONDS, new LinkedBlockingQueue(), provider); + } + + /** + * Adds a callback to the parameter provided, adding parameter to the queue if needed. + *

    + * This should always be synchronous. + */ + public void add(P parameter, C callback) { + Task task = tasks.get(parameter); + if (task == null) { + tasks.put(parameter, task = new Task(parameter)); + pool.execute(task); + } + task.callbacks.add(callback); + } + + /** + * This removes a particular callback from the specified parameter. + *

    + * If no callbacks remain for a given parameter, then the {@link CallBackProvider CallBackProvider's} stages may be omitted from execution. + * Stage 3 will have no callbacks, stage 2 will be skipped unless a {@link #get(Object)} is used, and stage 1 will be avoided on a best-effort basis. + *

    + * Subsequent calls to {@link #getSkipQueue(Object)} will always work. + *

    + * Subsequent calls to {@link #get(Object)} might work. + *

    + * This should always be synchronous + * @return true if no further execution for the parameter is possible, such that, no exceptions will be thrown in {@link #finishActive()} for the parameter, and {@link #get(Object)} will throw an {@link IllegalStateException}, false otherwise + * @throws IllegalStateException if parameter is not in the queue anymore + * @throws IllegalStateException if the callback was not specified for given parameter + */ + public boolean drop(P parameter, C callback) throws IllegalStateException { + final Task task = tasks.get(parameter); + if (task == null) { + return true; + } + if (!task.callbacks.remove(callback)) { + throw new IllegalStateException("Unknown " + callback + " for " + parameter); + } + if (task.callbacks.isEmpty()) { + return task.drop(); + } + return false; + } + + /** + * This method attempts to skip the waiting period for said parameter. + *

    + * This should always be synchronous. + * @throws IllegalStateException if the parameter is not in the queue anymore, or sometimes if called from asynchronous thread + */ + public T get(P parameter) throws E, IllegalStateException { + final Task task = tasks.get(parameter); + if (task == null) { + throw new IllegalStateException("Unknown " + parameter); + } + return task.get(); + } + + /** + * Processes a parameter as if it was in the queue, without ever passing to another thread. + */ + public T getSkipQueue(P parameter) throws E { + return skipQueue(parameter); + } + + /** + * Processes a parameter as if it was in the queue, without ever passing to another thread. + */ + public T getSkipQueue(P parameter, C callback) throws E { + final T object = skipQueue(parameter); + provider.callStage3(parameter, object, callback); + return object; + } + + /** + * Processes a parameter as if it was in the queue, without ever passing to another thread. + */ + public T getSkipQueue(P parameter, C...callbacks) throws E { + final CallBackProvider provider = this.provider; + final T object = skipQueue(parameter); + for (C callback : callbacks) { + provider.callStage3(parameter, object, callback); + } + return object; + } + + /** + * Processes a parameter as if it was in the queue, without ever passing to another thread. + */ + public T getSkipQueue(P parameter, Iterable callbacks) throws E { + final CallBackProvider provider = this.provider; + final T object = skipQueue(parameter); + for (C callback : callbacks) { + provider.callStage3(parameter, object, callback); + } + return object; + } + + private T skipQueue(P parameter) throws E { + Task task = tasks.get(parameter); + if (task != null) { + return task.get(); + } + T object = provider.callStage1(parameter); + provider.callStage2(parameter, object); + return object; + } + + /** + * This is the 'heartbeat' that should be called synchronously to finish any pending tasks + */ + public void finishActive() throws E { + final Queue finished = this.finished; + while (!finished.isEmpty()) { + finished.poll().finish(); + } + } + + public void setActiveThreads(final int coreSize) { + pool.setCorePoolSize(coreSize); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java new file mode 100644 index 00000000..d3fc469d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java @@ -0,0 +1,65 @@ +package org.bukkit.craftbukkit.util; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; +import org.bukkit.World; +import org.bukkit.block.BlockState; + +public class BlockStateListPopulator { + private final World world; + private final List list; + + public BlockStateListPopulator(World world) { + this(world, new ArrayList()); + } + + public BlockStateListPopulator(World world, List list) { + this.world = world; + this.list = list; + } + + public void setTypeAndData(int x, int y, int z, Block block, int data, int light) { + BlockState state = world.getBlockAt(x, y, z).getState(); + state.setTypeId(Block.getIdFromBlock(block)); + state.setRawData((byte) data); + list.add(state); + } + public void setTypeId(int x, int y, int z, int type) { + BlockState state = world.getBlockAt(x, y, z).getState(); + state.setTypeId(type); + list.add(state); + } + + public void setTypeUpdate(int x, int y, int z, Block block) { + this.setType(x, y, z, block); + } + + public void setTypeUpdate(BlockPos position, IBlockState data) { + setTypeAndData(position.getX(), position.getY(), position.getZ(), data.getBlock(), data.getBlock().getMetaFromState(data), 0); + + } + + public void setType(int x, int y, int z, Block block) { + BlockState state = world.getBlockAt(x, y, z).getState(); + state.setTypeId(Block.getIdFromBlock(block)); + list.add(state); + } + + public void updateList() { + for (BlockState state : list) { + state.update(true); + } + } + + public List getList() { + return list; + } + + public World getWorld() { + return world; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java new file mode 100644 index 00000000..5acf3c2c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java @@ -0,0 +1,256 @@ +package org.bukkit.craftbukkit.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +import com.google.common.collect.ImmutableMap; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.event.ClickEvent; +import org.bukkit.ChatColor; + +public final class CraftChatMessage { + + private static final Pattern LINK_PATTERN = Pattern.compile("((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(ChatColor.COLOR_CHAR) + " \\n]|$))))"); + private static final Map formatMap; + + static { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (TextFormatting format : TextFormatting.values()) { + builder.put(Character.toLowerCase(format.toString().charAt(1)), format); + } + formatMap = builder.build(); + } + + public static TextFormatting getColor(ChatColor color) { + return formatMap.get(color.getChar()); + } + + public static ChatColor getColor(TextFormatting format) { + return ChatColor.getByChar(format.formattingCode); + } + + private static class StringMessage { + private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(ChatColor.COLOR_CHAR) + "[0-9a-fk-or])|(\\n)|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(ChatColor.COLOR_CHAR) + " \\n]|$))))", Pattern.CASE_INSENSITIVE); + + private final List list = new ArrayList(); + private ITextComponent currentChatComponent = new TextComponentString(""); + private Style modifier = new Style(); + private final ITextComponent[] output; + private int currentIndex; + private final String message; + + private StringMessage(String message, boolean keepNewlines) { + this.message = message; + if (message == null) { + output = new ITextComponent[] { currentChatComponent }; + return; + } + list.add(currentChatComponent); + + Matcher matcher = INCREMENTAL_PATTERN.matcher(message); + String match = null; + while (matcher.find()) { + int groupId = 0; + while ((match = matcher.group(++groupId)) == null) { + // NOOP + } + appendNewComponent(matcher.start(groupId)); + switch (groupId) { + case 1: + TextFormatting format = formatMap.get(match.toLowerCase(java.util.Locale.ENGLISH).charAt(1)); + if (format == TextFormatting.RESET) { + modifier = new Style(); + } else if (format.isFancyStyling()) { + switch (format) { + case BOLD: + modifier.setBold(Boolean.TRUE); + break; + case ITALIC: + modifier.setItalic(Boolean.TRUE); + break; + case STRIKETHROUGH: + modifier.setStrikethrough(Boolean.TRUE); + break; + case UNDERLINE: + modifier.setUnderlined(Boolean.TRUE); + break; + case OBFUSCATED: + modifier.setObfuscated(Boolean.TRUE); + break; + default: + throw new AssertionError("Unexpected message format"); + } + } else { // Color resets formatting + modifier = new Style().setColor(format); + } + break; + case 2: + if (keepNewlines) { + currentChatComponent.appendSibling(new TextComponentString("\n")); + } else { + currentChatComponent = null; + } + break; + case 3: + if ( !( match.startsWith( "http://" ) || match.startsWith( "https://" ) ) ) { + match = "http://" + match; + } + modifier.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, match)); + appendNewComponent(matcher.end(groupId)); + modifier.setClickEvent(null); + } + currentIndex = matcher.end(groupId); + } + + if (currentIndex < message.length()) { + appendNewComponent(message.length()); + } + + output = list.toArray(new ITextComponent[list.size()]); + } + + private void appendNewComponent(int index) { + if (index <= currentIndex) { + return; + } + ITextComponent addition = new TextComponentString(message.substring(currentIndex, index)).setStyle(modifier); + currentIndex = index; + modifier = modifier.createShallowCopy(); + if (currentChatComponent == null) { + currentChatComponent = new TextComponentString(""); + list.add(currentChatComponent); + } + currentChatComponent.appendSibling(addition); + } + + private ITextComponent[] getOutput() { + return output; + } + } + + public static ITextComponent[] fromString(String message) { + return fromString(message, false); + } + + public static ITextComponent[] fromString(String message, boolean keepNewlines) { + return new StringMessage(message, keepNewlines).getOutput(); + } + + public static String fromComponent(ITextComponent component) { + return fromComponent(component, TextFormatting.BLACK); + } + + public static String fromComponent(ITextComponent component, TextFormatting defaultColor) { + if (component == null) return ""; + StringBuilder out = new StringBuilder(); + + for (ITextComponent c : (Iterable) component) { + Style modi = c.getStyle(); + out.append(modi.getColor() == null ? defaultColor : modi.getColor()); + if (modi.getBold()) { + out.append(TextFormatting.BOLD); + } + if (modi.getItalic()) { + out.append(TextFormatting.ITALIC); + } + if (modi.getUnderlined()) { + out.append(TextFormatting.UNDERLINE); + } + if (modi.getStrikethrough()) { + out.append(TextFormatting.STRIKETHROUGH); + } + if (modi.getObfuscated()) { + out.append(TextFormatting.OBFUSCATED); + } + out.append(c.getUnformattedComponentText()); + } + return out.toString().replaceFirst("^(" + defaultColor + ")*", ""); + } + + public static ITextComponent fixComponent(ITextComponent component) { + Matcher matcher = LINK_PATTERN.matcher(""); + return fixComponent(component, matcher); + } + + private static ITextComponent fixComponent(ITextComponent component, Matcher matcher) { + if (component instanceof TextComponentString) { + TextComponentString text = ((TextComponentString) component); + String msg = text.getText(); + if (matcher.reset(msg).find()) { + matcher.reset(); + + Style modifier = text.getStyle() != null ? + text.getStyle() : new Style(); + List extras = new ArrayList(); + List extrasOld = new ArrayList(text.getSiblings()); + component = text = new TextComponentString(""); + + int pos = 0; + while (matcher.find()) { + String match = matcher.group(); + + if ( !( match.startsWith( "http://" ) || match.startsWith( "https://" ) ) ) { + match = "http://" + match; + } + + TextComponentString prev = new TextComponentString(msg.substring(pos, matcher.start())); + prev.setStyle(modifier); + extras.add(prev); + + TextComponentString link = new TextComponentString(matcher.group()); + Style linkModi = modifier.createShallowCopy(); + linkModi.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, match)); + link.setStyle(linkModi); + extras.add(link); + + pos = matcher.end(); + } + + TextComponentString prev = new TextComponentString(msg.substring(pos)); + prev.setStyle(modifier); + extras.add(prev); + extras.addAll(extrasOld); + + for (ITextComponent c : extras) { + text.appendSibling(c); + } + } + } + + List extras = component.getSiblings(); + for (int i = 0; i < extras.size(); i++) { + ITextComponent comp = (ITextComponent) extras.get(i); + if (comp.getStyle() != null && comp.getStyle().getClickEvent() == null) { + extras.set(i, fixComponent(comp, matcher)); + } + } + + if (component instanceof TextComponentTranslation) { + Object[] subs = ((TextComponentTranslation) component).getFormatArgs(); + for (int i = 0; i < subs.length; i++) { + Object comp = subs[i]; + if (comp instanceof ITextComponent) { + ITextComponent c = (ITextComponent) comp; + if (c.getStyle() != null && c.getStyle().getClickEvent() == null) { + subs[i] = fixComponent(c, matcher); + } + } else if (comp instanceof String && matcher.reset((String)comp).find()) { + subs[i] = fixComponent(new TextComponentString((String) comp), matcher); + } + } + } + + return component; + } + + private CraftChatMessage() { + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftDamageSource.java b/src/main/java/org/bukkit/craftbukkit/util/CraftDamageSource.java new file mode 100644 index 00000000..92f36cb1 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftDamageSource.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.util; + +import net.minecraft.util.DamageSource; + +// Util class to create custom DamageSources. +public final class CraftDamageSource extends DamageSource { + public static DamageSource copyOf(final DamageSource original) { + CraftDamageSource newSource = new CraftDamageSource(original.damageType); + + // Check ignoresArmor + if (original.isUnblockable()) { + newSource.setDamageBypassesArmor(); + } + + // Check magic + if (original.isMagicDamage()) { + newSource.setMagicDamage(); + } + + // Check fire + if (original.isExplosion()) { + newSource.setExplosion(); + } + + return newSource; + } + + private CraftDamageSource(String identifier) { + super(identifier); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java new file mode 100644 index 00000000..f7ebf2aa --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java @@ -0,0 +1,12 @@ +package org.bukkit.craftbukkit.util; + +import org.bukkit.util.CachedServerIcon; + +public class CraftIconCache implements CachedServerIcon { + public final String value; + @Override + public String getData() { return value; } // Paper + public CraftIconCache(final String value) { + this.value = value; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java new file mode 100644 index 00000000..2cfd0361 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -0,0 +1,217 @@ +package org.bukkit.craftbukkit.util; + +import com.google.common.base.Charsets; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.io.Files; +import net.minecraft.advancements.AdvancementManager; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.item.Item; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTException; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.stats.StatList; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import org.bukkit.Achievement; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Statistic; +import org.bukkit.UnsafeValues; +import org.bukkit.advancement.Advancement; +import org.bukkit.craftbukkit.CraftStatistic; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.StringUtil; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +@SuppressWarnings("deprecation") +public final class CraftMagicNumbers implements UnsafeValues { + public static final UnsafeValues INSTANCE = new CraftMagicNumbers(); + + private CraftMagicNumbers() {} + + public static Block getBlock(org.bukkit.block.Block block) { + return getBlock(block.getType()); + } + + @Deprecated + // A bad method for bad magic. + public static Block getBlock(int id) { + return getBlock(Material.getMaterial(id)); + } + + @Deprecated + // A bad method for bad magic. + public static int getId(Block block) { + return Block.getIdFromBlock(block); + } + + public static Material getMaterial(Block block) { + return Material.getMaterial(Block.getIdFromBlock(block)); + } + + public static Item getItem(Material material) { + // TODO: Don't use ID + Item item = Item.getItemById(material.getId()); + return item; + } + + @Deprecated + // A bad method for bad magic. + public static Item getItem(int id) { + return Item.getItemById(id); + } + + @Deprecated + // A bad method for bad magic. + public static int getId(Item item) { + return Item.getIdFromItem(item); + } + + public static Material getMaterial(Item item) { + // TODO: Don't use ID + Material material = Material.getMaterial(Item.getIdFromItem(item)); + + if (material == null) { + return Material.AIR; + } + + return material; + } + + public static Block getBlock(Material material) { + if (material == null) { + return null; + } + // TODO: Don't use ID + Block block = Block.getBlockById(material.getId()); + + if (block == null) { + return Blocks.AIR; + } + + return block; + } + + @Override + public Material getMaterialFromInternalName(String name) { + return getMaterial((Item) Item.REGISTRY.getObject(new ResourceLocation(name))); + } + + @Override + public List tabCompleteInternalMaterialName(String token, List completions) { + ArrayList results = Lists.newArrayList(); + for (ResourceLocation key : Item.REGISTRY.getKeys()) { + results.add(key.toString()); + } + return StringUtil.copyPartialMatches(token, results, completions); + } + + @Override + public ItemStack modifyItemStack(ItemStack stack, String arguments) { + net.minecraft.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + try { + nmsStack.setTagCompound((NBTTagCompound) JsonToNBT.getTagFromJson(arguments)); + } catch (NBTException ex) { + Logger.getLogger(CraftMagicNumbers.class.getName()).log(Level.SEVERE, null, ex); + } + + stack.setItemMeta(CraftItemStack.getItemMeta(nmsStack)); + + return stack; + } + + @Override + public Statistic getStatisticFromInternalName(String name) { + return CraftStatistic.getBukkitStatisticByName(name); + } + + @Override + public Achievement getAchievementFromInternalName(String name) { + throw new UnsupportedOperationException("Not supported in this Minecraft version."); + } + + @Override + public List tabCompleteInternalStatisticOrAchievementName(String token, List completions) { + List matches = new ArrayList(); + Iterator iterator = StatList.ALL_STATS.iterator(); + while (iterator.hasNext()) { + String statistic = ((net.minecraft.stats.StatBase) iterator.next()).statId; + if (statistic.startsWith(token)) { + matches.add(statistic); + } + } + return matches; + } + + @Override + public Advancement loadAdvancement(NamespacedKey key, String advancement) { + if (Bukkit.getAdvancement(key) != null) { + throw new IllegalArgumentException("Advancement " + key + " already exists."); + } + + net.minecraft.advancements.Advancement.Builder nms = JsonUtils.gsonDeserialize(AdvancementManager.GSON, advancement, net.minecraft.advancements.Advancement.Builder.class); + if (nms != null) { + AdvancementManager.ADVANCEMENT_LIST.loadAdvancements(Maps.newHashMap(Collections.singletonMap(CraftNamespacedKey.toMinecraft(key), nms))); + Advancement bukkit = Bukkit.getAdvancement(key); + + if (bukkit != null) { + File file = new File(MinecraftServer.getServerCB().getAdvancementManager().advancementsDir, key.getNamespace() + File.separator + key.getKey() + ".json"); + file.getParentFile().mkdirs(); + + try { + Files.write(advancement, file, Charsets.UTF_8); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + + MinecraftServer.getServerCB().getPlayerList().reloadResources(); + + return bukkit; + } + } + + return null; + } + + @Override + public boolean removeAdvancement(NamespacedKey key) { + File file = new File(MinecraftServer.getServerCB().getAdvancementManager().advancementsDir, key.getNamespace() + File.separator + key.getKey() + ".json"); + return file.delete(); + } + + /** + * This helper class represents the different NBT Tags. + *

    + * These should match NBTBase#getTypeId + */ + public static class NBT { + + public static final int TAG_END = 0; + public static final int TAG_BYTE = 1; + public static final int TAG_SHORT = 2; + public static final int TAG_INT = 3; + public static final int TAG_LONG = 4; + public static final int TAG_FLOAT = 5; + public static final int TAG_DOUBLE = 6; + public static final int TAG_BYTE_ARRAY = 7; + public static final int TAG_STRING = 8; + public static final int TAG_LIST = 9; + public static final int TAG_COMPOUND = 10; + public static final int TAG_INT_ARRAY = 11; + public static final int TAG_ANY_NUMBER = 99; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java new file mode 100644 index 00000000..08f213c3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.util; + +import net.minecraft.util.ResourceLocation; +import org.bukkit.NamespacedKey; + +public final class CraftNamespacedKey { + + public CraftNamespacedKey() { + } + + public static NamespacedKey fromString(String string) { + return fromMinecraft(new ResourceLocation(string)); + } + + public static NamespacedKey fromMinecraft(ResourceLocation minecraft) { + return new NamespacedKey(minecraft.getResourceDomain(), minecraft.getResourcePath()); + } + + public static ResourceLocation toMinecraft(NamespacedKey key) { + return new ResourceLocation(key.getNamespace(), key.getKey()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/DatFileFilter.java b/src/main/java/org/bukkit/craftbukkit/util/DatFileFilter.java new file mode 100644 index 00000000..712c44f1 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/DatFileFilter.java @@ -0,0 +1,10 @@ +package org.bukkit.craftbukkit.util; + +import java.io.File; +import java.io.FilenameFilter; + +public class DatFileFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + return name.endsWith(".dat"); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ForwardLogHandler.java b/src/main/java/org/bukkit/craftbukkit/util/ForwardLogHandler.java new file mode 100644 index 00000000..74ce4b2e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ForwardLogHandler.java @@ -0,0 +1,52 @@ +package org.bukkit.craftbukkit.util; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class ForwardLogHandler extends ConsoleHandler { + private Map cachedLoggers = new ConcurrentHashMap(); + + private Logger getLogger(String name) { + Logger logger = cachedLoggers.get(name); + if (logger == null) { + logger = LogManager.getLogger(name); + cachedLoggers.put(name, logger); + } + + return logger; + } + + @Override + public void publish(LogRecord record) { + Logger logger = getLogger(String.valueOf(record.getLoggerName())); // See SPIGOT-1230 + Throwable exception = record.getThrown(); + Level level = record.getLevel(); + String message = getFormatter().formatMessage(record); + + if (level == Level.SEVERE) { + logger.error(message, exception); + } else if (level == Level.WARNING) { + logger.warn(message, exception); + } else if (level == Level.INFO) { + logger.info(message, exception); + } else if (level == Level.CONFIG) { + logger.debug(message, exception); + } else { + logger.trace(message, exception); + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java b/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java new file mode 100644 index 00000000..80a5c29f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/HashTreeSet.java @@ -0,0 +1,117 @@ +package org.bukkit.craftbukkit.util; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; + +public class HashTreeSet implements Set { + + private HashSet hash = new HashSet(); + private TreeSet tree = new TreeSet(); + + public HashTreeSet() { + + } + + @Override + public int size() { + return hash.size(); + } + + @Override + public boolean isEmpty() { + return hash.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return hash.contains(o); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private Iterator it = tree.iterator(); + private V last; + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public V next() { + return last = it.next(); + } + + @Override + public void remove() { + if (last == null) { + throw new IllegalStateException(); + } + it.remove(); + hash.remove(last); + last = null; + } + }; + } + + @Override + public Object[] toArray() { + return hash.toArray(); + } + + @Override + public Object[] toArray(Object[] a) { + return hash.toArray(a); + } + + @Override + public boolean add(V e) { + hash.add(e); + return tree.add(e); + } + + @Override + public boolean remove(Object o) { + hash.remove(o); + return tree.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return hash.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + tree.addAll(c); + return hash.addAll(c); + } + + @Override + public boolean retainAll(Collection c) { + tree.retainAll(c); + return hash.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + tree.removeAll(c); + return hash.removeAll(c); + } + + @Override + public void clear() { + hash.clear(); + tree.clear(); + } + + public V first() { + return tree.first(); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java new file mode 100644 index 00000000..2ae000d4 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java @@ -0,0 +1,97 @@ +package org.bukkit.craftbukkit.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +public abstract class LazyHashSet implements Set { + Set reference = null; + + public int size() { + return getReference().size(); + } + + public boolean isEmpty() { + return getReference().isEmpty(); + } + + public boolean contains(Object o) { + return getReference().contains(o); + } + + public Iterator iterator() { + return getReference().iterator(); + } + + public Object[] toArray() { + return getReference().toArray(); + } + + public T[] toArray(T[] a) { + return getReference().toArray(a); + } + + public boolean add(E o) { + return getReference().add(o); + } + + public boolean remove(Object o) { + return getReference().remove(o); + } + + public boolean containsAll(Collection c) { + return getReference().containsAll(c); + } + + public boolean addAll(Collection c) { + return getReference().addAll(c); + } + + public boolean retainAll(Collection c) { + return getReference().retainAll(c); + } + + public boolean removeAll(Collection c) { + return getReference().removeAll(c); + } + + public void clear() { + getReference().clear(); + } + + public Set getReference() { + Set reference = this.reference ; + if (reference != null) { + return reference; + } + return this.reference = makeReference(); + } + + abstract Set makeReference(); + + public boolean isLazy() { + return reference == null; + } + + @Override + public int hashCode() { + return 157 * getReference().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + LazyHashSet that = (LazyHashSet) obj; + return (this.isLazy() && that.isLazy()) || this.getReference().equals(that.getReference()); + } + + @Override + public String toString() { + return getReference().toString(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java new file mode 100644 index 00000000..65181682 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java @@ -0,0 +1,30 @@ +package org.bukkit.craftbukkit.util; + +import java.util.HashSet; +import java.util.List; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +import org.bukkit.entity.Player; + +public class LazyPlayerSet extends LazyHashSet { + + private final MinecraftServer server; + + public LazyPlayerSet(MinecraftServer server) { + this.server = server; + } + + @Override + HashSet makeReference() { + if (reference != null) { + throw new IllegalStateException("Reference already created!"); + } + List players = server.getPlayerList().getPlayers(); + HashSet reference = new HashSet(players.size()); + for (EntityPlayerMP player : players) { + reference.add(player.getBukkitEntity()); + } + return reference; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHash.java b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java new file mode 100644 index 00000000..691cafd0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java @@ -0,0 +1,15 @@ +package org.bukkit.craftbukkit.util; + +public class LongHash { + public static long toLong(int msw, int lsw) { + return ((long) msw << 32) + lsw - Integer.MIN_VALUE; + } + + public static int msw(long l) { + return (int) (l >> 32); + } + + public static int lsw(long l) { + return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java new file mode 100644 index 00000000..a2135792 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java @@ -0,0 +1,302 @@ +/* + Based on CompactHashSet Copyright 2011 Ontopia Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package org.bukkit.craftbukkit.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; + +public class LongHashSet { + private final static int INITIAL_SIZE = 3; + private final static double LOAD_FACTOR = 0.75; + + private final static long FREE = 0; + private final static long REMOVED = Long.MIN_VALUE; + + private int freeEntries; + private int elements; + private long[] values; + private int modCount; + + public LongHashSet() { + this(INITIAL_SIZE); + } + + public LongHashSet(int size) { + values = new long[(size==0 ? 1 : size)]; + elements = 0; + freeEntries = values.length; + modCount = 0; + } + + public Iterator iterator() { + return new Itr(); + } + + public int size() { + return elements; + } + + public boolean isEmpty() { + return elements == 0; + } + + public boolean contains(int msw, int lsw) { + return contains(LongHash.toLong(msw, lsw)); + } + + public boolean contains(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + return values[index] != FREE; + } + + public boolean add(int msw, int lsw) { + return add(LongHash.toLong(msw, lsw)); + } + + public boolean add(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + int deletedix = -1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + // if there's a deleted object here we can put this object here, + // provided it's not in here somewhere else already + if (values[index] == REMOVED) { + deletedix = index; + } + + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + if (values[index] == FREE) { + if (deletedix != -1) { // reusing a deleted cell + index = deletedix; + } else { + freeEntries--; + } + + modCount++; + elements++; + values[index] = value; + + if (1 - (freeEntries / (double) values.length) > LOAD_FACTOR) { + rehash(); + } + + return true; + } else { + return false; + } + } + + public void remove(int msw, int lsw) { + remove(LongHash.toLong(msw, lsw)); + } + + public boolean remove(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + if (values[index] != FREE) { + values[index] = REMOVED; + modCount++; + elements--; + return true; + } else { + return false; + } + } + + public void clear() { + elements = 0; + for (int ix = 0; ix < values.length; ix++) { + values[ix] = FREE; + } + + freeEntries = values.length; + modCount++; + } + + public long[] toArray() { + long[] result = new long[elements]; + long[] values = Arrays.copyOf(this.values, this.values.length); + int pos = 0; + + for (long value : values) { + if (value != FREE && value != REMOVED) { + result[pos++] = value; + } + } + + return result; + } + + public long popFirst() { + for (long value : values) { + if (value != FREE && value != REMOVED) { + remove(value); + return value; + } + } + + return 0; + } + + public long[] popAll() { + long[] ret = toArray(); + clear(); + return ret; + } + + // This method copied from Murmur3, written by Austin Appleby released under Public Domain + private int hash(long value) { + value ^= value >>> 33; + value *= 0xff51afd7ed558ccdL; + value ^= value >>> 33; + value *= 0xc4ceb9fe1a85ec53L; + value ^= value >>> 33; + return (int) value; + } + + private void rehash() { + int gargagecells = values.length - (elements + freeEntries); + if (gargagecells / (double) values.length > 0.05) { + rehash(values.length); + } else { + rehash(values.length * 2 + 1); + } + } + + private void rehash(int newCapacity) { + long[] newValues = new long[newCapacity]; + + for (long value : values) { + if (value == FREE || value == REMOVED) { + continue; + } + + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % newCapacity; + int offset = 1; + + // search for the object + while (newValues[index] != FREE) { + index = ((index + offset) & 0x7FFFFFFF) % newCapacity; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + newValues[index] = value; + } + + values = newValues; + freeEntries = values.length - elements; + } + + private class Itr implements Iterator { + private int index; + private int lastReturned = -1; + private int expectedModCount; + + public Itr() { + for (index = 0; index < values.length && (values[index] == FREE || values[index] == REMOVED); index++) { + // This is just to drive the index forward to the first valid entry + } + expectedModCount = modCount; + } + + public boolean hasNext() { + return index != values.length; + } + + public Long next() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + int length = values.length; + if (index >= length) { + lastReturned = -2; + throw new NoSuchElementException(); + } + + lastReturned = index; + for (index += 1; index < length && (values[index] == FREE || values[index] == REMOVED); index++) { + // This is just to drive the index forward to the next valid entry + } + + if (values[lastReturned] == FREE) { + return FREE; + } else { + return values[lastReturned]; + } + } + + public void remove() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + if (lastReturned == -1 || lastReturned == -2) { + throw new IllegalStateException(); + } + + if (values[lastReturned] != FREE && values[lastReturned] != REMOVED) { + values[lastReturned] = REMOVED; + elements--; + modCount++; + expectedModCount = modCount; + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java new file mode 100644 index 00000000..0cd430a3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java @@ -0,0 +1,422 @@ +package org.bukkit.craftbukkit.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class LongObjectHashMap implements Cloneable, Serializable { + static final long serialVersionUID = 2841537710170573815L; + + private static final long EMPTY_KEY = Long.MIN_VALUE; + private static final int BUCKET_SIZE = 4096; + + private transient long[][] keys; + private transient V[][] values; + private transient int modCount; + private transient int size; + + public LongObjectHashMap() { + initialize(); + } + + public LongObjectHashMap(Map map) { + this(); + putAll(map); + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public boolean containsKey(long key) { + return get(key) != null; + } + + public boolean containsValue(V value) { + for (V val : values()) { + if (val == value || val.equals(value)) { + return true; + } + } + + return false; + } + + public V get(long key) { + int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); + long[] inner = keys[index]; + if (inner == null) return null; + + for (int i = 0; i < inner.length; i++) { + long innerKey = inner[i]; + if (innerKey == EMPTY_KEY) { + return null; + } else if (innerKey == key) { + return values[index][i]; + } + } + + return null; + } + + public V put(long key, V value) { + int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); + long[] innerKeys = keys[index]; + V[] innerValues = values[index]; + modCount++; + + if (innerKeys == null) { + // need to make a new chain + keys[index] = innerKeys = new long[8]; + Arrays.fill(innerKeys, EMPTY_KEY); + values[index] = innerValues = (V[]) new Object[8]; + innerKeys[0] = key; + innerValues[0] = value; + size++; + } else { + int i; + for (i = 0; i < innerKeys.length; i++) { + // found an empty spot in the chain to put this + if (innerKeys[i] == EMPTY_KEY) { + size++; + innerKeys[i] = key; + innerValues[i] = value; + return null; + } + + // found an existing entry in the chain with this key, replace it + if (innerKeys[i] == key) { + V oldValue = innerValues[i]; + innerKeys[i] = key; + innerValues[i] = value; + return oldValue; + } + } + + // chain is full, resize it and add our new entry + keys[index] = innerKeys = Arrays.copyOf(innerKeys, i << 1); + Arrays.fill(innerKeys, i, innerKeys.length, EMPTY_KEY); + values[index] = innerValues = Arrays.copyOf(innerValues, i << 1); + innerKeys[i] = key; + innerValues[i] = value; + size++; + } + + return null; + } + + public V remove(long key) { + int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); + long[] inner = keys[index]; + if (inner == null) { + return null; + } + + for (int i = 0; i < inner.length; i++) { + // hit the end of the chain, didn't find this entry + if (inner[i] == EMPTY_KEY) { + break; + } + + if (inner[i] == key) { + V value = values[index][i]; + + for (i++; i < inner.length; i++) { + if (inner[i] == EMPTY_KEY) { + break; + } + + inner[i - 1] = inner[i]; + values[index][i - 1] = values[index][i]; + } + + inner[i - 1] = EMPTY_KEY; + values[index][i - 1] = null; + size--; + modCount++; + return value; + } + } + + return null; + } + + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + put((Long) entry.getKey(), (V) entry.getValue()); + } + } + + public void clear() { + if (size == 0) { + return; + } + + modCount++; + size = 0; + Arrays.fill(keys, null); + Arrays.fill(values, null); + } + + public Set keySet() { + return new KeySet(); + } + + public Collection values() { + return new ValueCollection(); + } + + /** + * Returns a Set of Entry objects for the HashMap. This is not how the internal + * implementation is laid out so this constructs the entire Set when called. For + * this reason it should be avoided if at all possible. + * + * @return Set of Entry objects + * @deprecated + */ + @Deprecated + public Set> entrySet() { + HashSet> set = new HashSet>(); + for (long key : keySet()) { + set.add(new Entry(key, get(key))); + } + + return set; + } + + public Object clone() throws CloneNotSupportedException { + LongObjectHashMap clone = (LongObjectHashMap) super.clone(); + // Make sure we clear any existing information from the clone + clone.clear(); + // Make sure the clone is properly setup for new entries + clone.initialize(); + + // Iterate through the data normally to do a safe clone + for (long key : keySet()) { + final V value = get(key); + clone.put(key, value); + } + + return clone; + } + + private void initialize() { + keys = new long[BUCKET_SIZE][]; + values = (V[][]) new Object[BUCKET_SIZE][]; + } + + private long keyIndex(long key) { + key ^= key >>> 33; + key *= 0xff51afd7ed558ccdL; + key ^= key >>> 33; + key *= 0xc4ceb9fe1a85ec53L; + key ^= key >>> 33; + return key; + } + + private void writeObject(ObjectOutputStream outputStream) throws IOException { + outputStream.defaultWriteObject(); + + for (long key : keySet()) { + V value = get(key); + outputStream.writeLong(key); + outputStream.writeObject(value); + } + + outputStream.writeLong(EMPTY_KEY); + outputStream.writeObject(null); + } + + private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException { + inputStream.defaultReadObject(); + initialize(); + + while (true) { + long key = inputStream.readLong(); + V value = (V) inputStream.readObject(); + if (key == EMPTY_KEY && value == null) { + break; + } + + put(key, value); + } + } + + + private class ValueIterator implements Iterator { + private int count; + private int index; + private int innerIndex; + private int expectedModCount; + private long lastReturned = EMPTY_KEY; + + long prevKey = EMPTY_KEY; + V prevValue; + + ValueIterator() { + expectedModCount = LongObjectHashMap.this.modCount; + } + + public boolean hasNext() { + return count < LongObjectHashMap.this.size; + } + + public void remove() { + if (LongObjectHashMap.this.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + if (lastReturned == EMPTY_KEY) { + throw new IllegalStateException(); + } + + count--; + LongObjectHashMap.this.remove(lastReturned); + lastReturned = EMPTY_KEY; + expectedModCount = LongObjectHashMap.this.modCount; + } + + public V next() { + if (LongObjectHashMap.this.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + if (!hasNext()) { + throw new NoSuchElementException(); + } + + long[][] keys = LongObjectHashMap.this.keys; + count++; + + if (prevKey != EMPTY_KEY) { + innerIndex++; + } + + for (; index < keys.length; index++) { + if (keys[index] != null) { + for (; innerIndex < keys[index].length; innerIndex++) { + long key = keys[index][innerIndex]; + V value = values[index][innerIndex]; + if (key == EMPTY_KEY) { + break; + } + + lastReturned = key; + prevKey = key; + prevValue = value; + return prevValue; + } + innerIndex = 0; + } + } + + throw new NoSuchElementException(); + } + } + + private class KeyIterator implements Iterator { + final ValueIterator iterator; + + public KeyIterator() { + iterator = new ValueIterator(); + } + + public void remove() { + iterator.remove(); + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Long next() { + iterator.next(); + return iterator.prevKey; + } + } + + + private class KeySet extends AbstractSet { + public void clear() { + LongObjectHashMap.this.clear(); + } + + public int size() { + return LongObjectHashMap.this.size(); + } + + public boolean contains(Object key) { + return key instanceof Long && LongObjectHashMap.this.containsKey((Long) key); + + } + + public boolean remove(Object key) { + return LongObjectHashMap.this.remove((Long) key) != null; + } + + public Iterator iterator() { + return new KeyIterator(); + } + } + + + private class ValueCollection extends AbstractCollection { + public void clear() { + LongObjectHashMap.this.clear(); + } + + public int size() { + return LongObjectHashMap.this.size(); + } + + public boolean contains(Object value) { + return LongObjectHashMap.this.containsValue((V) value); + } + + public Iterator iterator() { + return new ValueIterator(); + } + } + + + private class Entry implements Map.Entry { + private final Long key; + private V value; + + Entry(long k, V v) { + key = k; + value = v; + } + + public Long getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V v) { + V old = value; + value = v; + put(key, v); + return old; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/MojangNameLookup.java b/src/main/java/org/bukkit/craftbukkit/util/MojangNameLookup.java new file mode 100644 index 00000000..93a8f0bd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/MojangNameLookup.java @@ -0,0 +1,63 @@ +package org.bukkit.craftbukkit.util; + +import com.google.common.base.Charsets; +import com.google.gson.Gson; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.UUID; +import org.apache.commons.io.IOUtils; + +public class MojangNameLookup { + private static final Logger logger = LogManager.getFormatterLogger(MojangNameLookup.class); + + public static String lookupName(UUID id) { + if (id == null) { + return null; + } + + InputStream inputStream = null; + try { + URL url = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + id.toString().replace("-", "")); + URLConnection connection = url.openConnection(); + connection.setConnectTimeout(15000); + connection.setReadTimeout(15000); + connection.setUseCaches(false); + inputStream = connection.getInputStream(); + String result = IOUtils.toString(inputStream, Charsets.UTF_8); + Gson gson = new Gson(); + Response response = gson.fromJson(result, Response.class); + if (response == null || response.name == null) { + logger.warn("Failed to lookup name from UUID"); + return null; + } + + if (response.cause != null && response.cause.length() > 0) { + logger.warn("Failed to lookup name from UUID: %s", response.errorMessage); + return null; + } + + return response.name; + } catch (MalformedURLException ex) { + logger.warn("Malformed URL in UUID lookup"); + return null; + } catch (IOException ex) { + IOUtils.closeQuietly(inputStream); + } finally { + IOUtils.closeQuietly(inputStream); + } + + return null; + } + + private class Response { + String errorMessage; + String cause; + String name; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java new file mode 100644 index 00000000..9ef32c53 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.util; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.MinecraftException; + +public class ServerShutdownThread extends Thread { + private final MinecraftServer server; + + public ServerShutdownThread(MinecraftServer server) { + this.server = server; + } + + @Override + public void run() { + try { + server.stopServer(); + } catch (MinecraftException ex) { + ex.printStackTrace(); + } /*finally { + try { + server.reader.getTerminal().restore(); + } catch (Exception e) { + } + }*/ + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java new file mode 100644 index 00000000..2dbfef96 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java @@ -0,0 +1,61 @@ +package org.bukkit.craftbukkit.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; +import joptsimple.OptionException; +import joptsimple.OptionSet; +import net.minecraft.server.MinecraftServer; + +public class ShortConsoleLogFormatter extends Formatter { + private final SimpleDateFormat date; + + public ShortConsoleLogFormatter(MinecraftServer server) { + OptionSet options = server.options; + SimpleDateFormat date = null; + + if (options.has("date-format")) { + try { + Object object = options.valueOf("date-format"); + + if ((object != null) && (object instanceof SimpleDateFormat)) { + date = (SimpleDateFormat) object; + } + } catch (OptionException ex) { + System.err.println("Given date format is not valid. Falling back to default."); + } + } else if (options.has("nojline")) { + date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + + if (date == null) { + date = new SimpleDateFormat("HH:mm:ss"); + } + + this.date = date; + } + + @Override + public String format(LogRecord record) { + StringBuilder builder = new StringBuilder(); + Throwable ex = record.getThrown(); + + builder.append(date.format(record.getMillis())); + builder.append(" ["); + builder.append(record.getLevel().getLocalizedName().toUpperCase()); + builder.append("] "); + builder.append(formatMessage(record)); + builder.append('\n'); + + if (ex != null) { + StringWriter writer = new StringWriter(); + ex.printStackTrace(new PrintWriter(writer)); + builder.append(writer); + } + + return builder.toString(); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/StructureGrowDelegate.java b/src/main/java/org/bukkit/craftbukkit/util/StructureGrowDelegate.java new file mode 100644 index 00000000..c6de9560 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/StructureGrowDelegate.java @@ -0,0 +1,69 @@ +package org.bukkit.craftbukkit.util; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; +import org.bukkit.BlockChangeDelegate; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.material.MaterialData; + +public class StructureGrowDelegate implements BlockChangeDelegate { + private final CraftWorld world; + private final List blocks = new ArrayList(); + + public StructureGrowDelegate(World world) { + this.world = world.getWorld(); + } + + public boolean setRawTypeId(int x, int y, int z, int type) { + return setRawTypeIdAndData(x, y, z, type, 0); + } + + public boolean setRawTypeIdAndData(int x, int y, int z, int type, int data) { + BlockState state = world.getBlockAt(x, y, z).getState(); + state.setTypeId(type); + state.setData(new MaterialData(type, (byte) data)); + blocks.add(state); + return true; + } + + public boolean setTypeId(int x, int y, int z, int typeId) { + return setRawTypeId(x, y, z, typeId); + } + + public boolean setTypeIdAndData(int x, int y, int z, int typeId, int data) { + return setRawTypeIdAndData(x, y, z, typeId, data); + } + + public int getTypeId(int x, int y, int z) { + for (BlockState state : blocks) { + if (state.getX() == x && state.getY() == y && state.getZ() == z) { + return state.getTypeId(); + } + } + + return world.getBlockTypeIdAt(x, y, z); + } + + public int getHeight() { + return world.getMaxHeight(); + } + + public List getBlocks() { + return blocks; + } + + public boolean isEmpty(int x, int y, int z) { + for (BlockState state : blocks) { + if (state.getX() == x && state.getY() == y && state.getZ() == z) { + return Block.getBlockById(state.getTypeId()) == Blocks.AIR; + } + } + + return world.getBlockAt(x, y, z).isEmpty(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java new file mode 100644 index 00000000..dd9f4a74 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java @@ -0,0 +1,55 @@ +package org.bukkit.craftbukkit.util; + +import com.mojang.util.QueueLogAppender; +import jline.console.ConsoleReader; +import org.bukkit.craftbukkit.Main; +import org.bukkit.craftbukkit.command.ColouredConsoleSender; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TerminalConsoleWriterThread implements Runnable { + private static final byte[] RESET_LINE = String.valueOf('\r').getBytes(); + final private ConsoleReader reader; + final private OutputStream output; + + public TerminalConsoleWriterThread(OutputStream output, ConsoleReader reader) { + this.output = output; + this.reader = reader; + } + + @Override + public void run() { + String message; + + // Using name from log4j config in vanilla jar + while (true) { + message = QueueLogAppender.getNextLogEvent("TerminalConsole"); + if (message == null) { + continue; + } + + try { + if (Main.useJline) { + this.output.write(RESET_LINE); + this.output.write(ColouredConsoleSender.toAnsiStr(message).getBytes()); + this.output.flush(); + + try { + reader.drawLine(); + } catch (Throwable ex) { + reader.getCursorBuffer().clear(); + } + reader.flush(); + } else { + output.write(message.getBytes()); + output.flush(); + } + } catch (IOException ex) { + Logger.getLogger(TerminalConsoleWriterThread.class.getName()).log(Level.SEVERE, null, ex); + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java new file mode 100644 index 00000000..08d10560 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java @@ -0,0 +1,278 @@ +package org.bukkit.craftbukkit.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.RandomAccess; + +// implementation of an ArrayList that offers a getter without range checks +@SuppressWarnings("unchecked") +public class UnsafeList extends AbstractList implements List, RandomAccess, Cloneable, Serializable { + private static final long serialVersionUID = 8683452581112892191L; + + private transient Object[] data; + private int size; + private int initialCapacity; + + private Iterator[] iterPool = new Iterator[1]; + private int maxPool; + private int poolCounter; + + public UnsafeList(int capacity, int maxIterPool) { + super(); + if (capacity < 0) capacity = 32; + int rounded = Integer.highestOneBit(capacity - 1) << 1; + data = new Object[rounded]; + initialCapacity = rounded; + maxPool = maxIterPool; + iterPool[0] = new Itr(); + } + + public UnsafeList(int capacity) { + this(capacity, 5); + } + + public UnsafeList() { + this(32); + } + + public E get(int index) { + rangeCheck(index); + + return (E) data[index]; + } + + public E unsafeGet(int index) { + return (E) data[index]; + } + + public E set(int index, E element) { + rangeCheck(index); + + E old = (E) data[index]; + data[index] = element; + return old; + } + + public boolean add(E element) { + growIfNeeded(); + data[size++] = element; + return true; + } + + public void add(int index, E element) { + growIfNeeded(); + System.arraycopy(data, index, data, index + 1, size - index); + data[index] = element; + size++; + } + + public E remove(int index) { + rangeCheck(index); + + E old = (E) data[index]; + int movedCount = size - index - 1; + if (movedCount > 0) { + System.arraycopy(data, index + 1, data, index, movedCount); + } + data[--size] = null; + + return old; + } + + public boolean remove(Object o) { + int index = indexOf(o); + if (index >= 0) { + remove(index); + return true; + } + + return false; + } + + public int indexOf(Object o) { + for (int i = 0; i < size; i++) { + if (o == data[i] || o.equals(data[i])) { + return i; + } + } + + return -1; + } + + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + public void clear() { + // Create new array to reset memory usage to initial capacity + size = 0; + + // If array has grown too large create new one, otherwise just clear it + if (data.length > initialCapacity << 3) { + data = new Object[initialCapacity]; + } else { + for (int i = 0; i < data.length; i++) { + data[i] = null; + } + } + } + + // actually rounds up to nearest power of two + public void trimToSize() { + int old = data.length; + int rounded = Integer.highestOneBit(size - 1) << 1; + if (rounded < old) { + data = Arrays.copyOf(data, rounded); + } + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public Object clone() throws CloneNotSupportedException { + UnsafeList copy = (UnsafeList) super.clone(); + copy.data = Arrays.copyOf(data, size); + copy.size = size; + copy.initialCapacity = initialCapacity; + copy.iterPool = new Iterator[1]; + copy.iterPool[0] = new Itr(); + copy.maxPool = maxPool; + copy.poolCounter = 0; + return copy; + } + + public Iterator iterator() { + // Try to find an iterator that isn't in use + for (Iterator iter : iterPool) { + if (!((Itr) iter).valid) { + Itr iterator = (Itr) iter; + iterator.reset(); + return iterator; + } + } + + // Couldn't find one, see if we can grow our pool size + if (iterPool.length < maxPool) { + Iterator[] newPool = new Iterator[iterPool.length + 1]; + System.arraycopy(iterPool, 0, newPool, 0, iterPool.length); + iterPool = newPool; + + iterPool[iterPool.length - 1] = new Itr(); + return iterPool[iterPool.length - 1]; + } + + // Still couldn't find a free one, round robin replace one with a new iterator + // This is done in the hope that the new one finishes so can be reused + poolCounter = ++poolCounter % iterPool.length; + iterPool[poolCounter] = new Itr(); + return iterPool[poolCounter]; + } + + private void rangeCheck(int index) { + if (index >= size || index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + } + + private void growIfNeeded() { + if (size == data.length) { + Object[] newData = new Object[data.length << 1]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + } + + private void writeObject(ObjectOutputStream os) throws IOException { + os.defaultWriteObject(); + + os.writeInt(size); + os.writeInt(initialCapacity); + for (int i = 0; i < size; i++) { + os.writeObject(data[i]); + } + os.writeInt(maxPool); + } + + private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { + is.defaultReadObject(); + + size = is.readInt(); + initialCapacity = is.readInt(); + data = new Object[Integer.highestOneBit(size - 1) << 1]; + for (int i = 0; i < size; i++) { + data[i] = is.readObject(); + } + maxPool = is.readInt(); + iterPool = new Iterator[1]; + iterPool[0] = new Itr(); + } + + public class Itr implements Iterator { + int index; + int lastRet = -1; + int expectedModCount = modCount; + public boolean valid = true; + + public void reset() { + index = 0; + lastRet = -1; + expectedModCount = modCount; + valid = true; + } + + public boolean hasNext() { + valid = index != size; + return valid; + } + + public E next() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + int i = index; + if (i >= size) { + throw new NoSuchElementException(); + } + + if (i >= data.length) { + throw new ConcurrentModificationException(); + } + + index = i + 1; + return (E) data[lastRet = i]; + } + + public void remove() { + if (lastRet < 0) { + throw new IllegalStateException(); + } + + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + try { + UnsafeList.this.remove(lastRet); + index = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java new file mode 100644 index 00000000..a50165ad --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -0,0 +1,7 @@ +package org.bukkit.craftbukkit.util; + +public final class Versioning { + public static String getBukkitVersion() { + return "1.12.2-R0.1"; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/Waitable.java b/src/main/java/org/bukkit/craftbukkit/util/Waitable.java new file mode 100644 index 00000000..5cd11543 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/Waitable.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.util; + +import java.util.concurrent.ExecutionException; + + +public abstract class Waitable implements Runnable { + private enum Status { + WAITING, + RUNNING, + FINISHED, + } + Throwable t = null; + T value = null; + Status status = Status.WAITING; + + public final void run() { + synchronized (this) { + if (status != Status.WAITING) { + throw new IllegalStateException("Invalid state " + status); + } + status = Status.RUNNING; + } + try { + value = evaluate(); + } catch (Throwable t) { + this.t = t; + } finally { + synchronized (this) { + status = Status.FINISHED; + this.notifyAll(); + } + } + } + + protected abstract T evaluate(); + + public synchronized T get() throws InterruptedException, ExecutionException { + while (status != Status.FINISHED) { + this.wait(); + } + if (t != null) { + throw new ExecutionException(t); + } + return value; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java b/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java new file mode 100644 index 00000000..1b781ba8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java @@ -0,0 +1,169 @@ +package org.bukkit.craftbukkit.util; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.lang3.Validate; + +public final class WeakCollection implements Collection { + static final Object NO_VALUE = new Object(); + private final Collection> collection; + + public WeakCollection() { + collection = new ArrayList>(); + } + + public boolean add(T value) { + Validate.notNull(value, "Cannot add null value"); + return collection.add(new WeakReference(value)); + } + + public boolean addAll(Collection collection) { + Collection> values = this.collection; + boolean ret = false; + for (T value : collection) { + Validate.notNull(value, "Cannot add null value"); + ret |= values.add(new WeakReference(value)); + } + return ret; + } + + public void clear() { + collection.clear(); + } + + public boolean contains(Object object) { + if (object == null) { + return false; + } + for (T compare : this) { + if (object.equals(compare)) { + return true; + } + } + return false; + } + + public boolean containsAll(Collection collection) { + return toCollection().containsAll(collection); + } + + public boolean isEmpty() { + return !iterator().hasNext(); + } + + public Iterator iterator() { + return new Iterator() { + Iterator> it = collection.iterator(); + Object value = NO_VALUE; + + public boolean hasNext() { + Object value = this.value; + if (value != null && value != NO_VALUE) { + return true; + } + + Iterator> it = this.it; + value = null; + + while (it.hasNext()) { + WeakReference ref = it.next(); + value = ref.get(); + if (value == null) { + it.remove(); + } else { + this.value = value; + return true; + } + } + return false; + } + + public T next() throws NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException("No more elements"); + } + + @SuppressWarnings("unchecked") + T value = (T) this.value; + this.value = NO_VALUE; + return value; + } + + public void remove() throws IllegalStateException { + if (value != NO_VALUE) { + throw new IllegalStateException("No last element"); + } + + value = null; + it.remove(); + } + }; + } + + public boolean remove(Object object) { + if (object == null) { + return false; + } + + Iterator it = this.iterator(); + while (it.hasNext()) { + if (object.equals(it.next())) { + it.remove(); + return true; + } + } + return false; + } + + public boolean removeAll(Collection collection) { + Iterator it = this.iterator(); + boolean ret = false; + while (it.hasNext()) { + if (collection.contains(it.next())) { + ret = true; + it.remove(); + } + } + return ret; + } + + public boolean retainAll(Collection collection) { + Iterator it = this.iterator(); + boolean ret = false; + while (it.hasNext()) { + if (!collection.contains(it.next())) { + ret = true; + it.remove(); + } + } + return ret; + } + + public int size() { + int s = 0; + for (T value : this) { + s++; + } + return s; + } + + public Object[] toArray() { + return this.toArray(new Object[0]); + } + + public T[] toArray(T[] array) { + return toCollection().toArray(array); + } + + private Collection toCollection() { + ArrayList collection = new ArrayList(); + for (T value : this) { + collection.add(value); + } + return collection; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java new file mode 100644 index 00000000..71392907 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.util.permissions.DefaultPermissions; + +public final class CommandPermissions { + private static final String ROOT = "minecraft.command"; + private static final String PREFIX = ROOT + "."; + + private CommandPermissions() {} + + public static Permission registerPermissions(Permission parent) { + Permission commands = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all vanilla minecraft commands", parent); + + DefaultPermissions.registerPermission(PREFIX + "kill", "Allows the user to commit suicide", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "me", "Allows the user to perform a chat action", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "tell", "Allows the user to privately message another player", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "say", "Allows the user to talk as the console", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "give", "Allows the user to give items to players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "teleport", "Allows the user to teleport players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "kick", "Allows the user to kick players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "stop", "Allows the user to stop the server", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "xp", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "toggledownfall", "Allows the user to toggle rain on/off for a given world", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "effect", "Allows the user to add/remove effects on players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "selector", "Allows the use of selectors", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "trigger", "Allows the use of the trigger command", PermissionDefault.TRUE, commands); + + commands.recalculatePermissibles(); + return commands; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java new file mode 100644 index 00000000..a3f534eb --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java @@ -0,0 +1,18 @@ +package org.bukkit.craftbukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.util.permissions.DefaultPermissions; + +public final class CraftDefaultPermissions { + private static final String ROOT= "minecraft"; + + private CraftDefaultPermissions() {} + + public static void registerCorePermissions() { + Permission parent = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all vanilla utilities and commands"); + CommandPermissions.registerPermissions(parent); + DefaultPermissions.registerPermission(ROOT + ".autocraft", "Gives the user the ability to use autocraft functionality", PermissionDefault.OP, parent); + parent.recalculatePermissibles(); + } +} diff --git a/src/main/java/org/bukkit/enchantments/Enchantment.java b/src/main/java/org/bukkit/enchantments/Enchantment.java new file mode 100644 index 00000000..ac3a7f99 --- /dev/null +++ b/src/main/java/org/bukkit/enchantments/Enchantment.java @@ -0,0 +1,339 @@ +package org.bukkit.enchantments; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.inventory.ItemStack; + +/** + * The various type of enchantments that may be added to armour or weapons + */ +public abstract class Enchantment { + /** + * Provides protection against environmental damage + */ + public static final Enchantment PROTECTION_ENVIRONMENTAL = new EnchantmentWrapper(0); + + /** + * Provides protection against fire damage + */ + public static final Enchantment PROTECTION_FIRE = new EnchantmentWrapper(1); + + /** + * Provides protection against fall damage + */ + public static final Enchantment PROTECTION_FALL = new EnchantmentWrapper(2); + + /** + * Provides protection against explosive damage + */ + public static final Enchantment PROTECTION_EXPLOSIONS = new EnchantmentWrapper(3); + + /** + * Provides protection against projectile damage + */ + public static final Enchantment PROTECTION_PROJECTILE = new EnchantmentWrapper(4); + + /** + * Decreases the rate of air loss whilst underwater + */ + public static final Enchantment OXYGEN = new EnchantmentWrapper(5); + + /** + * Increases the speed at which a player may mine underwater + */ + public static final Enchantment WATER_WORKER = new EnchantmentWrapper(6); + + /** + * Damages the attacker + */ + public static final Enchantment THORNS = new EnchantmentWrapper(7); + + /** + * Increases walking speed while in water + */ + public static final Enchantment DEPTH_STRIDER = new EnchantmentWrapper(8); + + /** + * Freezes any still water adjacent to ice / frost which player is walking on + */ + public static final Enchantment FROST_WALKER = new EnchantmentWrapper(9); + + /** + * Item cannot be removed + */ + public static final Enchantment BINDING_CURSE = new EnchantmentWrapper(10); + + /** + * Increases damage against all targets + */ + public static final Enchantment DAMAGE_ALL = new EnchantmentWrapper(16); + + /** + * Increases damage against undead targets + */ + public static final Enchantment DAMAGE_UNDEAD = new EnchantmentWrapper(17); + + /** + * Increases damage against arthropod targets + */ + public static final Enchantment DAMAGE_ARTHROPODS = new EnchantmentWrapper(18); + + /** + * All damage to other targets will knock them back when hit + */ + public static final Enchantment KNOCKBACK = new EnchantmentWrapper(19); + + /** + * When attacking a target, has a chance to set them on fire + */ + public static final Enchantment FIRE_ASPECT = new EnchantmentWrapper(20); + + /** + * Provides a chance of gaining extra loot when killing monsters + */ + public static final Enchantment LOOT_BONUS_MOBS = new EnchantmentWrapper(21); + + /** + * Increases damage against targets when using a sweep attack + */ + public static final Enchantment SWEEPING_EDGE = new EnchantmentWrapper(22); + + /** + * Increases the rate at which you mine/dig + */ + public static final Enchantment DIG_SPEED = new EnchantmentWrapper(32); + + /** + * Allows blocks to drop themselves instead of fragments (for example, + * stone instead of cobblestone) + */ + public static final Enchantment SILK_TOUCH = new EnchantmentWrapper(33); + + /** + * Decreases the rate at which a tool looses durability + */ + public static final Enchantment DURABILITY = new EnchantmentWrapper(34); + + /** + * Provides a chance of gaining extra loot when destroying blocks + */ + public static final Enchantment LOOT_BONUS_BLOCKS = new EnchantmentWrapper(35); + + /** + * Provides extra damage when shooting arrows from bows + */ + public static final Enchantment ARROW_DAMAGE = new EnchantmentWrapper(48); + + /** + * Provides a knockback when an entity is hit by an arrow from a bow + */ + public static final Enchantment ARROW_KNOCKBACK = new EnchantmentWrapper(49); + + /** + * Sets entities on fire when hit by arrows shot from a bow + */ + public static final Enchantment ARROW_FIRE = new EnchantmentWrapper(50); + + /** + * Provides infinite arrows when shooting a bow + */ + public static final Enchantment ARROW_INFINITE = new EnchantmentWrapper(51); + + /** + * Decreases odds of catching worthless junk + */ + public static final Enchantment LUCK = new EnchantmentWrapper(61); + + /** + * Increases rate of fish biting your hook + */ + public static final Enchantment LURE = new EnchantmentWrapper(62); + + /** + * Allows mending the item using experience orbs + */ + public static final Enchantment MENDING = new EnchantmentWrapper(70); + + /** + * Item disappears instead of dropping + */ + public static final Enchantment VANISHING_CURSE = new EnchantmentWrapper(71); + + private static final Map byId = new HashMap(); + private static final Map byName = new HashMap(); + private static boolean acceptingNew = true; + private final int id; + + public Enchantment(int id) { + this.id = id; + } + + /** + * Gets the unique ID of this enchantment + * + * @return Unique ID + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Gets the unique name of this enchantment + * + * @return Unique name + */ + public abstract String getName(); + + /** + * Gets the maximum level that this Enchantment may become. + * + * @return Maximum level of the Enchantment + */ + public abstract int getMaxLevel(); + + /** + * Gets the level that this Enchantment should start at + * + * @return Starting level of the Enchantment + */ + public abstract int getStartLevel(); + + /** + * Gets the type of {@link ItemStack} that may fit this Enchantment. + * + * @return Target type of the Enchantment + */ + public abstract EnchantmentTarget getItemTarget(); + + /** + * Checks if this enchantment is a treasure enchantment. + *
    + * Treasure enchantments can only be received via looting, trading, or + * fishing. + * + * @return true if the enchantment is a treasure enchantment + */ + public abstract boolean isTreasure(); + + /** + * Checks if this enchantment is a cursed enchantment + *
    + * Cursed enchantments are found the same way treasure enchantments are + * + * @return true if the enchantment is cursed + */ + public abstract boolean isCursed(); + + /** + * Check if this enchantment conflicts with another enchantment. + * + * @param other The enchantment to check against + * @return True if there is a conflict. + */ + public abstract boolean conflictsWith(Enchantment other); + + /** + * Checks if this Enchantment may be applied to the given {@link + * ItemStack}. + *

    + * This does not check if it conflicts with any enchantments already + * applied to the item. + * + * @param item Item to test + * @return True if the enchantment may be applied, otherwise False + */ + public abstract boolean canEnchantItem(ItemStack item); + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof Enchantment)) { + return false; + } + final Enchantment other = (Enchantment) obj; + if (this.id != other.id) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "Enchantment[" + id + ", " + getName() + "]"; + } + + /** + * Registers an enchantment with the given ID and object. + *

    + * Generally not to be used from within a plugin. + * + * @param enchantment Enchantment to register + */ + public static void registerEnchantment(Enchantment enchantment) { + if (byId.containsKey(enchantment.id) || byName.containsKey(enchantment.getName())) { + throw new IllegalArgumentException("Cannot set already-set enchantment"); + } else if (!isAcceptingRegistrations()) { + throw new IllegalStateException("No longer accepting new enchantments (can only be done by the server implementation)"); + } + + byId.put(enchantment.id, enchantment); + byName.put(enchantment.getName(), enchantment); + } + + /** + * Checks if this is accepting Enchantment registrations. + * + * @return True if the server Implementation may add enchantments + */ + public static boolean isAcceptingRegistrations() { + return acceptingNew; + } + + /** + * Stops accepting any enchantment registrations + */ + public static void stopAcceptingRegistrations() { + acceptingNew = false; + } + + /** + * Gets the Enchantment at the specified ID + * + * @param id ID to fetch + * @return Resulting Enchantment, or null if not found + * @deprecated Magic value + */ + @Deprecated + public static Enchantment getById(int id) { + return byId.get(id); + } + + /** + * Gets the Enchantment at the specified name + * + * @param name Name to fetch + * @return Resulting Enchantment, or null if not found + */ + public static Enchantment getByName(String name) { + return byName.get(name); + } + + /** + * Gets an array of all the registered {@link Enchantment}s + * + * @return Array of enchantments + */ + public static Enchantment[] values() { + return byId.values().toArray(new Enchantment[byId.size()]); + } +} diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java b/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java new file mode 100644 index 00000000..189788da --- /dev/null +++ b/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java @@ -0,0 +1,81 @@ +package org.bukkit.enchantments; + +import org.apache.commons.lang3.Validate; + +/** + * A class for the available enchantment offers in the enchantment table. + */ +public class EnchantmentOffer { + + private Enchantment enchantment; + private int enchantmentLevel; + private int cost; + + public EnchantmentOffer(Enchantment enchantment, int enchantmentLevel, int cost) { + this.enchantment = enchantment; + this.enchantmentLevel = enchantmentLevel; + this.cost = cost; + } + + /** + * Get the type of the enchantment. + * + * @return type of enchantment + */ + public Enchantment getEnchantment() { + return enchantment; + } + + /** + * Sets the type of the enchantment. + * + * @param enchantment type of the enchantment + */ + public void setEnchantment(Enchantment enchantment) { + Validate.notNull(enchantment, "The enchantment may not be null!"); + + this.enchantment = enchantment; + } + + /** + * Gets the level of the enchantment. + * + * @return level of the enchantment + */ + public int getEnchantmentLevel() { + return enchantmentLevel; + } + + /** + * Sets the level of the enchantment. + * + * @param enchantmentLevel level of the enchantment + */ + public void setEnchantmentLevel(int enchantmentLevel) { + Validate.isTrue(enchantmentLevel > 0, "The enchantment level must be greater than 0!"); + + this.enchantmentLevel = enchantmentLevel; + } + + /** + * Gets the cost in experience levels the player has to pay to enchant his + * item with this enchantment. + * + * @return cost for this enchantment + */ + public int getCost() { + return cost; + } + + /** + * Sets the cost in experience levels the player has to pay to enchant his + * item with this enchantment + * + * @param cost cost for this enchantment + */ + public void setCost(int cost) { + Validate.isTrue(cost > 0, "The cost must be greater than 0!"); + + this.cost = cost; + } +} diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java new file mode 100644 index 00000000..61067912 --- /dev/null +++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java @@ -0,0 +1,196 @@ +package org.bukkit.enchantments; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +/** + * Represents the applicable target for a {@link Enchantment} + */ +public enum EnchantmentTarget { + /** + * Allows the Enchantment to be placed on all items + */ + ALL { + @Override + public boolean includes(Material item) { + return true; + } + }, + + /** + * Allows the Enchantment to be placed on armor + */ + ARMOR { + @Override + public boolean includes(Material item) { + return ARMOR_FEET.includes(item) + || ARMOR_LEGS.includes(item) + || ARMOR_HEAD.includes(item) + || ARMOR_TORSO.includes(item); + } + }, + + /** + * Allows the Enchantment to be placed on feet slot armor + */ + ARMOR_FEET { + @Override + public boolean includes(Material item) { + return item.equals(Material.LEATHER_BOOTS) + || item.equals(Material.CHAINMAIL_BOOTS) + || item.equals(Material.IRON_BOOTS) + || item.equals(Material.DIAMOND_BOOTS) + || item.equals(Material.GOLD_BOOTS); + } + }, + + /** + * Allows the Enchantment to be placed on leg slot armor + */ + ARMOR_LEGS { + @Override + public boolean includes(Material item) { + return item.equals(Material.LEATHER_LEGGINGS) + || item.equals(Material.CHAINMAIL_LEGGINGS) + || item.equals(Material.IRON_LEGGINGS) + || item.equals(Material.DIAMOND_LEGGINGS) + || item.equals(Material.GOLD_LEGGINGS); + } + }, + + /** + * Allows the Enchantment to be placed on torso slot armor + */ + ARMOR_TORSO { + @Override + public boolean includes(Material item) { + return item.equals(Material.LEATHER_CHESTPLATE) + || item.equals(Material.CHAINMAIL_CHESTPLATE) + || item.equals(Material.IRON_CHESTPLATE) + || item.equals(Material.DIAMOND_CHESTPLATE) + || item.equals(Material.GOLD_CHESTPLATE); + } + }, + + /** + * Allows the Enchantment to be placed on head slot armor + */ + ARMOR_HEAD { + @Override + public boolean includes(Material item) { + return item.equals(Material.LEATHER_HELMET) + || item.equals(Material.CHAINMAIL_HELMET) + || item.equals(Material.DIAMOND_HELMET) + || item.equals(Material.IRON_HELMET) + || item.equals(Material.GOLD_HELMET); + } + }, + + /** + * Allows the Enchantment to be placed on weapons (swords) + */ + WEAPON { + @Override + public boolean includes(Material item) { + return item.equals(Material.WOOD_SWORD) + || item.equals(Material.STONE_SWORD) + || item.equals(Material.IRON_SWORD) + || item.equals(Material.DIAMOND_SWORD) + || item.equals(Material.GOLD_SWORD); + } + }, + + /** + * Allows the Enchantment to be placed on tools (spades, pickaxe, hoes, + * axes) + */ + TOOL { + @Override + public boolean includes(Material item) { + return item.equals(Material.WOOD_SPADE) + || item.equals(Material.STONE_SPADE) + || item.equals(Material.IRON_SPADE) + || item.equals(Material.DIAMOND_SPADE) + || item.equals(Material.GOLD_SPADE) + || item.equals(Material.WOOD_PICKAXE) + || item.equals(Material.STONE_PICKAXE) + || item.equals(Material.IRON_PICKAXE) + || item.equals(Material.DIAMOND_PICKAXE) + || item.equals(Material.GOLD_PICKAXE) + || item.equals(Material.WOOD_HOE) + || item.equals(Material.STONE_HOE) + || item.equals(Material.IRON_HOE) + || item.equals(Material.DIAMOND_HOE) + || item.equals(Material.GOLD_HOE) + || item.equals(Material.WOOD_AXE) + || item.equals(Material.STONE_AXE) + || item.equals(Material.IRON_AXE) + || item.equals(Material.DIAMOND_AXE) + || item.equals(Material.GOLD_AXE) + || item.equals(Material.SHEARS) + || item.equals(Material.FLINT_AND_STEEL); + } + }, + + /** + * Allows the Enchantment to be placed on bows. + */ + BOW { + @Override + public boolean includes(Material item) { + return item.equals(Material.BOW); + } + }, + + /** + * Allows the Enchantment to be placed on fishing rods. + */ + FISHING_ROD { + @Override + public boolean includes(Material item) { + return item.equals(Material.FISHING_ROD); + } + }, + + /** + * Allows the enchantment to be placed on items with durability. + */ + BREAKABLE { + @Override + public boolean includes(Material item) { + return item.getMaxDurability() > 0 && item.getMaxStackSize() == 1; + } + }, + + /** + * Allows the enchantment to be placed on wearable items. + */ + WEARABLE { + @Override + public boolean includes(Material item) { + return ARMOR.includes(item) + || item.equals(Material.ELYTRA) + || item.equals(Material.PUMPKIN) + || item.equals(Material.JACK_O_LANTERN) + || item.equals(Material.SKULL_ITEM); + } + }; + + /** + * Check whether this target includes the specified item. + * + * @param item The item to check + * @return True if the target includes the item + */ + public abstract boolean includes(Material item); + + /** + * Check whether this target includes the specified item. + * + * @param item The item to check + * @return True if the target includes the item + */ + public boolean includes(ItemStack item) { + return includes(item.getType()); + } +} diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java b/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java new file mode 100644 index 00000000..3984e919 --- /dev/null +++ b/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java @@ -0,0 +1,61 @@ +package org.bukkit.enchantments; + +import org.bukkit.inventory.ItemStack; + +/** + * A simple wrapper for ease of selecting {@link Enchantment}s + */ +public class EnchantmentWrapper extends Enchantment { + public EnchantmentWrapper(int id) { + super(id); + } + + /** + * Gets the enchantment bound to this wrapper + * + * @return Enchantment + */ + public Enchantment getEnchantment() { + return Enchantment.getById(getId()); + } + + @Override + public int getMaxLevel() { + return getEnchantment().getMaxLevel(); + } + + @Override + public int getStartLevel() { + return getEnchantment().getStartLevel(); + } + + @Override + public EnchantmentTarget getItemTarget() { + return getEnchantment().getItemTarget(); + } + + @Override + public boolean canEnchantItem(ItemStack item) { + return getEnchantment().canEnchantItem(item); + } + + @Override + public String getName() { + return getEnchantment().getName(); + } + + @Override + public boolean isTreasure() { + return getEnchantment().isTreasure(); + } + + @Override + public boolean isCursed() { + return getEnchantment().isCursed(); + } + + @Override + public boolean conflictsWith(Enchantment other) { + return getEnchantment().conflictsWith(other); + } +} diff --git a/src/main/java/org/bukkit/entity/AbstractHorse.java b/src/main/java/org/bukkit/entity/AbstractHorse.java new file mode 100644 index 00000000..10757454 --- /dev/null +++ b/src/main/java/org/bukkit/entity/AbstractHorse.java @@ -0,0 +1,103 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.AbstractHorseInventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a Horse-like creature. + */ +public interface AbstractHorse extends Animals, Vehicle, InventoryHolder, Tameable { + + /** + * Gets the horse's variant. + *

    + * A horse's variant defines its physical appearance and capabilities. + * Whether a horse is a regular horse, donkey, mule, or other kind of horse + * is determined using the variant. + * + * @return a {@link Horse.Variant} representing the horse's variant + * @deprecated different variants are different classes + */ + @Deprecated + public Horse.Variant getVariant(); + + /** + * @param variant + * @deprecated you are required to spawn a different entity + */ + @Deprecated + public void setVariant(Horse.Variant variant); + + /** + * Gets the domestication level of this horse. + *

    + * A higher domestication level indicates that the horse is closer to + * becoming tame. As the domestication level gets closer to the max + * domestication level, the chance of the horse becoming tame increases. + * + * @return domestication level + */ + public int getDomestication(); + + /** + * Sets the domestication level of this horse. + *

    + * Setting the domestication level to a high value will increase the + * horse's chances of becoming tame. + *

    + * Domestication level must be greater than zero and no greater than + * the max domestication level of the horse, determined with + * {@link #getMaxDomestication()} + * + * @param level domestication level + */ + public void setDomestication(int level); + + /** + * Gets the maximum domestication level of this horse. + *

    + * The higher this level is, the longer it will likely take + * for the horse to be tamed. + * + * @return the max domestication level + */ + public int getMaxDomestication(); + + /** + * Sets the maximum domestication level of this horse. + *

    + * Setting a higher max domestication will increase the amount of + * domesticating (feeding, riding, etc.) necessary in order to tame it, + * while setting a lower max value will have the opposite effect. + *

    + * Maximum domestication must be greater than zero. + * + * @param level the max domestication level + */ + public void setMaxDomestication(int level); + + /** + * Gets the jump strength of this horse. + *

    + * Jump strength defines how high the horse can jump. A higher jump strength + * increases how high a jump will go. + * + * @return the horse's jump strength + */ + public double getJumpStrength(); + + /** + * Sets the jump strength of this horse. + *

    + * A higher jump strength increases how high a jump will go. + * Setting a jump strength to 0 will result in no jump. + * You cannot set a jump strength to a value below 0 or + * above 2. + * + * @param strength jump strength for this horse + */ + public void setJumpStrength(double strength); + + @Override + public AbstractHorseInventory getInventory(); +} diff --git a/src/main/java/org/bukkit/entity/Ageable.java b/src/main/java/org/bukkit/entity/Ageable.java new file mode 100644 index 00000000..e9fccb29 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Ageable.java @@ -0,0 +1,67 @@ +package org.bukkit.entity; + +/** + * Represents an entity that can age and breed. + */ +public interface Ageable extends Creature { + /** + * Gets the age of this animal. + * + * @return Age + */ + public int getAge(); + + /** + * Sets the age of this animal. + * + * @param age New age + */ + public void setAge(int age); + + /** + * Lock the age of the animal, setting this will prevent the animal from + * maturing or getting ready for mating. + * + * @param lock new lock + */ + public void setAgeLock(boolean lock); + + /** + * Gets the current agelock. + * + * @return the current agelock + */ + public boolean getAgeLock(); + + /** + * Sets the age of the animal to a baby + */ + public void setBaby(); + + /** + * Sets the age of the animal to an adult + */ + public void setAdult(); + + /** + * Returns true if the animal is an adult. + * + * @return return true if the animal is an adult + */ + public boolean isAdult(); + + /** + * Return the ability to breed of the animal. + * + * @return the ability to breed of the animal + */ + public boolean canBreed(); + + /** + * Set breedability of the animal, if the animal is a baby and set to + * breed it will instantly grow up. + * + * @param breed breedability of the animal + */ + public void setBreed(boolean breed); +} diff --git a/src/main/java/org/bukkit/entity/Ambient.java b/src/main/java/org/bukkit/entity/Ambient.java new file mode 100644 index 00000000..779e3897 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Ambient.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an ambient mob + */ +public interface Ambient extends LivingEntity {} diff --git a/src/main/java/org/bukkit/entity/AnimalTamer.java b/src/main/java/org/bukkit/entity/AnimalTamer.java new file mode 100644 index 00000000..5f74f0d8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/AnimalTamer.java @@ -0,0 +1,20 @@ +package org.bukkit.entity; + +import java.util.UUID; + +public interface AnimalTamer { + + /** + * This is the name of the specified AnimalTamer. + * + * @return The name to reference on tamed animals or null if a name cannot be obtained + */ + public String getName(); + + /** + * This is the UUID of the specified AnimalTamer. + * + * @return The UUID to reference on tamed animals + */ + public UUID getUniqueId(); +} diff --git a/src/main/java/org/bukkit/entity/Animals.java b/src/main/java/org/bukkit/entity/Animals.java new file mode 100644 index 00000000..f0dc157d --- /dev/null +++ b/src/main/java/org/bukkit/entity/Animals.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an Animal. + */ +public interface Animals extends Ageable {} diff --git a/src/main/java/org/bukkit/entity/AreaEffectCloud.java b/src/main/java/org/bukkit/entity/AreaEffectCloud.java new file mode 100644 index 00000000..f88be2ff --- /dev/null +++ b/src/main/java/org/bukkit/entity/AreaEffectCloud.java @@ -0,0 +1,225 @@ +package org.bukkit.entity; + +import java.util.List; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; + +/** + * Represents an area effect cloud which will imbue a potion effect onto + * entities which enter it. + */ +public interface AreaEffectCloud extends Entity { + + /** + * Gets the duration which this cloud will exist for (in ticks). + * + * @return cloud duration + */ + int getDuration(); + + /** + * Sets the duration which this cloud will exist for (in ticks). + * + * @param duration cloud duration + */ + void setDuration(int duration); + + /** + * Gets the time which an entity has to be exposed to the cloud before the + * effect is applied. + * + * @return wait time + */ + int getWaitTime(); + + /** + * Sets the time which an entity has to be exposed to the cloud before the + * effect is applied. + * + * @param waitTime wait time + */ + void setWaitTime(int waitTime); + + /** + * Gets the time that an entity will be immune from subsequent exposure. + * + * @return reapplication delay + */ + int getReapplicationDelay(); + + /** + * Sets the time that an entity will be immune from subsequent exposure. + * + * @param delay reapplication delay + */ + void setReapplicationDelay(int delay); + + /** + * Gets the amount that the duration of this cloud will decrease by when it + * applies an effect to an entity. + * + * @return duration on use delta + */ + int getDurationOnUse(); + + /** + * Sets the amount that the duration of this cloud will decrease by when it + * applies an effect to an entity. + * + * @param duration duration on use delta + */ + void setDurationOnUse(int duration); + + /** + * Gets the initial radius of the cloud. + * + * @return radius + */ + float getRadius(); + + /** + * Sets the initial radius of the cloud. + * + * @param radius radius + */ + void setRadius(float radius); + + /** + * Gets the amount that the radius of this cloud will decrease by when it + * applies an effect to an entity. + * + * @return radius on use delta + */ + float getRadiusOnUse(); + + /** + * Sets the amount that the radius of this cloud will decrease by when it + * applies an effect to an entity. + * + * @param radius radius on use delta + */ + void setRadiusOnUse(float radius); + + /** + * Gets the amount that the radius of this cloud will decrease by each tick. + * + * @return radius per tick delta + */ + float getRadiusPerTick(); + + /** + * Gets the amount that the radius of this cloud will decrease by each tick. + * + * @param radius per tick delta + */ + void setRadiusPerTick(float radius); + + /** + * Gets the particle which this cloud will be composed of + * + * @return particle the set particle type + */ + Particle getParticle(); + + /** + * Sets the particle which this cloud will be composed of + * + * @param particle the new particle type + */ + void setParticle(Particle particle); + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + PotionData getBasePotionData(); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this cloud. + *

    + * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + List getCustomEffects(); + + /** + * Adds a custom potion effect to this cloud. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the effect was added as a result of this call + */ + boolean addCustomEffect(PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this cloud. + * + * @param type the potion effect type to remove + * @return true if the an effect was removed as a result of this call + */ + boolean removeCustomEffect(PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this cloud. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(PotionEffectType type); + + /** + * Removes all custom potion effects from this cloud. + */ + void clearCustomEffects(); + + /** + * Gets the color of this cloud. Will be applied as a tint to its particles. + * + * @return cloud color + */ + Color getColor(); + + /** + * Sets the color of this cloud. Will be applied as a tint to its particles. + * + * @param color cloud color + */ + void setColor(Color color); + + /** + * Retrieve the original source of this cloud. + * + * @return the {@link ProjectileSource} that threw the LingeringPotion + */ + public ProjectileSource getSource(); + + /** + * Set the original source of this cloud. + * + * @param source the {@link ProjectileSource} that threw the LingeringPotion + */ + public void setSource(ProjectileSource source); +} diff --git a/src/main/java/org/bukkit/entity/ArmorStand.java b/src/main/java/org/bukkit/entity/ArmorStand.java new file mode 100644 index 00000000..de5506f7 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ArmorStand.java @@ -0,0 +1,261 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.EulerAngle; + +public interface ArmorStand extends LivingEntity { + + /** + * Returns the item the armor stand is + * currently holding + * + * @return the held item + */ + ItemStack getItemInHand(); + + /** + * Sets the item the armor stand is currently + * holding + * + * @param item the item to hold + */ + void setItemInHand(ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its feet + * + * @return the worn item + */ + ItemStack getBoots(); + + /** + * Sets the item currently being worn + * by the armor stand on its feet + * + * @param item the item to wear + */ + void setBoots(ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its legs + * + * @return the worn item + */ + ItemStack getLeggings(); + + /** + * Sets the item currently being worn + * by the armor stand on its legs + * + * @param item the item to wear + */ + void setLeggings(ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its chest + * + * @return the worn item + */ + ItemStack getChestplate(); + + /** + * Sets the item currently being worn + * by the armor stand on its chest + * + * @param item the item to wear + */ + void setChestplate(ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its head + * + * @return the worn item + */ + ItemStack getHelmet(); + + /** + * Sets the item currently being worn + * by the armor stand on its head + * + * @param item the item to wear + */ + void setHelmet(ItemStack item); + + /** + * Returns the armor stand's body's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getBodyPose(); + + /** + * Sets the armor stand's body's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setBodyPose(EulerAngle pose); + + /** + * Returns the armor stand's left arm's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getLeftArmPose(); + + /** + * Sets the armor stand's left arm's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setLeftArmPose(EulerAngle pose); + + /** + * Returns the armor stand's right arm's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getRightArmPose(); + + /** + * Sets the armor stand's right arm's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setRightArmPose(EulerAngle pose); + + /** + * Returns the armor stand's left leg's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getLeftLegPose(); + + /** + * Sets the armor stand's left leg's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setLeftLegPose(EulerAngle pose); + + /** + * Returns the armor stand's right leg's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getRightLegPose(); + + /** + * Sets the armor stand's right leg's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setRightLegPose(EulerAngle pose); + + /** + * Returns the armor stand's head's + * current pose as a {@link EulerAngle} + * + * @return the current pose + */ + EulerAngle getHeadPose(); + + /** + * Sets the armor stand's head's + * current pose as a {@link EulerAngle} + * + * @param pose the current pose + */ + void setHeadPose(EulerAngle pose); + + /** + * Returns whether the armor stand has + * a base plate + * + * @return whether it has a base plate + */ + boolean hasBasePlate(); + + /** + * Sets whether the armor stand has a + * base plate + * + * @param basePlate whether is has a base plate + */ + void setBasePlate(boolean basePlate); + + /** + * Returns whether the armor stand should be + * visible or not + * + * @return whether the stand is visible or not + */ + boolean isVisible(); + + /** + * Sets whether the armor stand should be + * visible or not + * + * @param visible whether the stand is visible or not + */ + void setVisible(boolean visible); + + /** + * Returns whether this armor stand has arms + * + * @return whether this has arms or not + */ + boolean hasArms(); + + /** + * Sets whether this armor stand has arms + * + * @param arms whether this has arms or not + */ + void setArms(boolean arms); + + /** + * Returns whether this armor stand is scaled + * down + * + * @return whether this is scaled down + */ + boolean isSmall(); + + /** + * Sets whether this armor stand is scaled + * down + * + * @param small whether this is scaled down + */ + void setSmall(boolean small); + + /** + * Returns whether this armor stand is a marker, + * meaning it has a very small collision box + * + * @return whether this is a marker + */ + boolean isMarker(); + + /** + * Sets whether this armor stand is a marker, + * meaning it has a very small collision box + * + * @param marker whether this is a marker + */ + void setMarker(boolean marker); +} diff --git a/src/main/java/org/bukkit/entity/Arrow.java b/src/main/java/org/bukkit/entity/Arrow.java new file mode 100644 index 00000000..99814eee --- /dev/null +++ b/src/main/java/org/bukkit/entity/Arrow.java @@ -0,0 +1,111 @@ +package org.bukkit.entity; + +import org.bukkit.block.Block; + +/** + * Represents an arrow. + */ +public interface Arrow extends Projectile { + + /** + * Gets the knockback strength for an arrow, which is the + * {@link org.bukkit.enchantments.Enchantment#KNOCKBACK KnockBack} level + * of the bow that shot it. + * + * @return the knockback strength value + */ + public int getKnockbackStrength(); + + /** + * Sets the knockback strength for an arrow. + * + * @param knockbackStrength the knockback strength value + */ + public void setKnockbackStrength(int knockbackStrength); + + /** + * Gets whether this arrow is critical. + *

    + * Critical arrows have increased damage and cause particle effects. + *

    + * Critical arrows generally occur when a player fully draws a bow before + * firing. + * + * @return true if it is critical + */ + public boolean isCritical(); + + /** + * Sets whether or not this arrow should be critical. + * + * @param critical whether or not it should be critical + */ + public void setCritical(boolean critical); + + /** + * Gets whether this arrow is in a block or not. + *

    + * Arrows in a block are motionless and may be picked up by players. + * + * @return true if in a block + */ + public boolean isInBlock(); + + /** + * Gets the block to which this arrow is attached. + * + * @return the attached block or null if not attached + */ + public Block getAttachedBlock(); + + /** + * Gets the current pickup status of this arrow. + * + * @return the pickup status of this arrow. + */ + public PickupStatus getPickupStatus(); + + /** + * Sets the current pickup status of this arrow. + * + * @param status new pickup status of this arrow. + */ + public void setPickupStatus(PickupStatus status); + + /** + * Represents the pickup status of this arrow. + */ + public enum PickupStatus { + /** + * The arrow cannot be picked up. + */ + DISALLOWED, + /** + * The arrow can be picked up. + */ + ALLOWED, + /** + * The arrow can only be picked up by players in creative mode. + */ + CREATIVE_ONLY + } + + // Spigot start + public class Spigot extends Entity.Spigot + { + + public double getDamage() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void setDamage(double damage) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @Override + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/entity/Bat.java b/src/main/java/org/bukkit/entity/Bat.java new file mode 100644 index 00000000..bd73f22e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Bat.java @@ -0,0 +1,27 @@ +package org.bukkit.entity; + +/** + * Represents a Bat + */ +public interface Bat extends Ambient { + + /** + * Checks the current waking state of this bat. + *

    + * This does not imply any persistence of state past the method call. + * + * @return true if the bat is awake or false if it is currently hanging + * from a block + */ + boolean isAwake(); + + /** + * This method modifies the current waking state of this bat. + *

    + * This does not prevent a bat from spontaneously awaking itself, or from + * reattaching itself to a block. + * + * @param state the new state + */ + void setAwake(boolean state); +} diff --git a/src/main/java/org/bukkit/entity/Blaze.java b/src/main/java/org/bukkit/entity/Blaze.java new file mode 100644 index 00000000..7a5505b7 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Blaze.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a Blaze monster + */ +public interface Blaze extends Monster { + +} diff --git a/src/main/java/org/bukkit/entity/Boat.java b/src/main/java/org/bukkit/entity/Boat.java new file mode 100644 index 00000000..db02115e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Boat.java @@ -0,0 +1,104 @@ +package org.bukkit.entity; + +import org.bukkit.TreeSpecies; + +/** + * Represents a boat entity. + */ +public interface Boat extends Vehicle { + + /** + * Gets the wood type of the boat. + * + * @return the wood type + */ + TreeSpecies getWoodType(); + + /** + * Sets the wood type of the boat. + * + * @param species the new wood type + */ + void setWoodType(TreeSpecies species); + + /** + * Gets the maximum speed of a boat. The speed is unrelated to the + * velocity. + * + * @return The max speed. + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getMaxSpeed(); + + /** + * Sets the maximum speed of a boat. Must be nonnegative. Default is 0.4D. + * + * @param speed The max speed. + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setMaxSpeed(double speed); + + /** + * Gets the deceleration rate (newSpeed = curSpeed * rate) of occupied + * boats. The default is 0.2. + * + * @return The rate of deceleration + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getOccupiedDeceleration(); + + /** + * Sets the deceleration rate (newSpeed = curSpeed * rate) of occupied + * boats. Setting this to a higher value allows for quicker acceleration. + * The default is 0.2. + * + * @param rate deceleration rate + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setOccupiedDeceleration(double rate); + + /** + * Gets the deceleration rate (newSpeed = curSpeed * rate) of unoccupied + * boats. The default is -1. Values below 0 indicate that no additional + * deceleration is imposed. + * + * @return The rate of deceleration + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getUnoccupiedDeceleration(); + + /** + * Sets the deceleration rate (newSpeed = curSpeed * rate) of unoccupied + * boats. Setting this to a higher value allows for quicker deceleration + * of boats when a player disembarks. The default is -1. Values below 0 + * indicate that no additional deceleration is imposed. + * + * @param rate deceleration rate + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setUnoccupiedDeceleration(double rate); + + /** + * Get whether boats can work on land. + * + * @return whether boats can work on land + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public boolean getWorkOnLand(); + + /** + * Set whether boats can work on land. + * + * @param workOnLand whether boats can work on land + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setWorkOnLand(boolean workOnLand); +} diff --git a/src/main/java/org/bukkit/entity/CaveSpider.java b/src/main/java/org/bukkit/entity/CaveSpider.java new file mode 100644 index 00000000..9c376469 --- /dev/null +++ b/src/main/java/org/bukkit/entity/CaveSpider.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Spider. + */ +public interface CaveSpider extends Spider {} diff --git a/src/main/java/org/bukkit/entity/ChestedHorse.java b/src/main/java/org/bukkit/entity/ChestedHorse.java new file mode 100644 index 00000000..24780af6 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ChestedHorse.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * Represents Horse-like creatures which can carry an inventory. + */ +public interface ChestedHorse extends AbstractHorse { + + /** + * Gets whether the horse has a chest equipped. + * + * @return true if the horse has chest storage + */ + public boolean isCarryingChest(); + + /** + * Sets whether the horse has a chest equipped. Removing a chest will also + * clear the chest's inventory. + * + * @param chest true if the horse should have a chest + */ + public void setCarryingChest(boolean chest); +} diff --git a/src/main/java/org/bukkit/entity/Chicken.java b/src/main/java/org/bukkit/entity/Chicken.java new file mode 100644 index 00000000..cb3ec6ef --- /dev/null +++ b/src/main/java/org/bukkit/entity/Chicken.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Chicken. + */ +public interface Chicken extends Animals {} diff --git a/src/main/java/org/bukkit/entity/ComplexEntityPart.java b/src/main/java/org/bukkit/entity/ComplexEntityPart.java new file mode 100644 index 00000000..f4ab0bbd --- /dev/null +++ b/src/main/java/org/bukkit/entity/ComplexEntityPart.java @@ -0,0 +1,14 @@ +package org.bukkit.entity; + +/** + * Represents a single part of a {@link ComplexLivingEntity} + */ +public interface ComplexEntityPart extends Entity { + + /** + * Gets the parent {@link ComplexLivingEntity} of this part. + * + * @return Parent complex entity + */ + public ComplexLivingEntity getParent(); +} diff --git a/src/main/java/org/bukkit/entity/ComplexLivingEntity.java b/src/main/java/org/bukkit/entity/ComplexLivingEntity.java new file mode 100644 index 00000000..f74411c3 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ComplexLivingEntity.java @@ -0,0 +1,16 @@ +package org.bukkit.entity; + +import java.util.Set; + +/** + * Represents a complex living entity - one that is made up of various smaller + * parts + */ +public interface ComplexLivingEntity extends LivingEntity { + /** + * Gets a list of parts that belong to this complex entity + * + * @return List of parts + */ + public Set getParts(); +} diff --git a/src/main/java/org/bukkit/entity/Cow.java b/src/main/java/org/bukkit/entity/Cow.java new file mode 100644 index 00000000..cd4ed4de --- /dev/null +++ b/src/main/java/org/bukkit/entity/Cow.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Cow. + */ +public interface Cow extends Animals {} diff --git a/src/main/java/org/bukkit/entity/Creature.java b/src/main/java/org/bukkit/entity/Creature.java new file mode 100644 index 00000000..f223f55b --- /dev/null +++ b/src/main/java/org/bukkit/entity/Creature.java @@ -0,0 +1,26 @@ +package org.bukkit.entity; + +/** + * Represents a Creature. Creatures are non-intelligent monsters or animals + * which have very simple abilities. + */ +public interface Creature extends LivingEntity { + + /** + * Instructs this Creature to set the specified LivingEntity as its + * target. + *

    + * Hostile creatures may attack their target, and friendly creatures may + * follow their target. + * + * @param target New LivingEntity to target, or null to clear the target + */ + public void setTarget(LivingEntity target); + + /** + * Gets the current target of this Creature + * + * @return Current target of this creature, or null if none exists + */ + public LivingEntity getTarget(); +} diff --git a/src/main/java/org/bukkit/entity/Creeper.java b/src/main/java/org/bukkit/entity/Creeper.java new file mode 100644 index 00000000..f957d836 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Creeper.java @@ -0,0 +1,53 @@ +package org.bukkit.entity; + +/** + * Represents a Creeper + */ +public interface Creeper extends Monster { + + /** + * Checks if this Creeper is powered (Electrocuted) + * + * @return true if this creeper is powered + */ + public boolean isPowered(); + + /** + * Sets the Powered status of this Creeper + * + * @param value New Powered status + */ + public void setPowered(boolean value); + + /** + * Set the maximum fuse ticks for this Creeper, where the maximum ticks + * is the amount of time in which a creeper is allowed to be in the + * primed state before exploding. + * + * @param ticks the new maximum fuse ticks + */ + public void setMaxFuseTicks(int ticks); + + /** + * Get the maximum fuse ticks for this Creeper, where the maximum ticks + * is the amount of time in which a creeper is allowed to be in the + * primed state before exploding. + * + * @return the maximum fuse ticks + */ + public int getMaxFuseTicks(); + + /** + * Set the explosion radius in which this Creeper's explosion will affect. + * + * @param radius the new explosion radius + */ + public void setExplosionRadius(int radius); + + /** + * Get the explosion radius in which this Creeper's explosion will affect. + * + * @return the explosion radius + */ + public int getExplosionRadius(); +} diff --git a/src/main/java/org/bukkit/entity/Damageable.java b/src/main/java/org/bukkit/entity/Damageable.java new file mode 100644 index 00000000..e173cf01 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Damageable.java @@ -0,0 +1,72 @@ +package org.bukkit.entity; + +import org.bukkit.attribute.Attribute; + +/** + * Represents an {@link Entity} that has health and can take damage. + */ +public interface Damageable extends Entity { + /** + * Deals the given amount of damage to this entity. + * + * @param amount Amount of damage to deal + */ + void damage(double amount); + + /** + * Deals the given amount of damage to this entity, from a specified + * entity. + * + * @param amount Amount of damage to deal + * @param source Entity which to attribute this damage from + */ + void damage(double amount, Entity source); + + /** + * Gets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is dead. + * + * @return Health represented from 0 to max + */ + double getHealth(); + + /** + * Sets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is + * dead. + * + * @param health New health represented from 0 to max + * @throws IllegalArgumentException Thrown if the health is {@literal < 0 or >} + * {@link #getMaxHealth()} + */ + void setHealth(double health); + + /** + * Gets the maximum health this entity has. + * + * @return Maximum health + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + double getMaxHealth(); + + /** + * Sets the maximum health this entity can have. + *

    + * If the health of the entity is above the value provided it will be set + * to that value. + *

    + * Note: An entity with a health bar ({@link Player}, {@link EnderDragon}, + * {@link Wither}, etc...} will have their bar scaled accordingly. + * + * @param health amount of health to set the maximum to + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + void setMaxHealth(double health); + + /** + * Resets the max health to the original amount. + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + void resetMaxHealth(); +} diff --git a/src/main/java/org/bukkit/entity/Donkey.java b/src/main/java/org/bukkit/entity/Donkey.java new file mode 100644 index 00000000..b554e330 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Donkey.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Donkey - variant of {@link ChestedHorse}. + */ +public interface Donkey extends ChestedHorse { } diff --git a/src/main/java/org/bukkit/entity/DragonFireball.java b/src/main/java/org/bukkit/entity/DragonFireball.java new file mode 100644 index 00000000..6c475a37 --- /dev/null +++ b/src/main/java/org/bukkit/entity/DragonFireball.java @@ -0,0 +1,3 @@ +package org.bukkit.entity; + +public interface DragonFireball extends Fireball {} diff --git a/src/main/java/org/bukkit/entity/Egg.java b/src/main/java/org/bukkit/entity/Egg.java new file mode 100644 index 00000000..2dcc00b9 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Egg.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown egg. + */ +public interface Egg extends Projectile {} diff --git a/src/main/java/org/bukkit/entity/ElderGuardian.java b/src/main/java/org/bukkit/entity/ElderGuardian.java new file mode 100644 index 00000000..5ca1d4c9 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ElderGuardian.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an ElderGuardian - variant of {@link Guardian}. + */ +public interface ElderGuardian extends Guardian { } diff --git a/src/main/java/org/bukkit/entity/EnderCrystal.java b/src/main/java/org/bukkit/entity/EnderCrystal.java new file mode 100644 index 00000000..684dd764 --- /dev/null +++ b/src/main/java/org/bukkit/entity/EnderCrystal.java @@ -0,0 +1,41 @@ +package org.bukkit.entity; + +import org.bukkit.Location; + +/** + * A crystal that heals nearby EnderDragons + */ +public interface EnderCrystal extends Entity { + + /** + * Return whether or not this end crystal is showing the + * bedrock slate underneath it. + * + * @return true if the bottom is being shown + */ + boolean isShowingBottom(); + + /** + * Sets whether or not this end crystal is showing the + * bedrock slate underneath it. + * + * @param showing whether the bedrock slate should be shown + */ + void setShowingBottom(boolean showing); + + /** + * Gets the location that this end crystal is pointing its beam to. + * + * @return the location that the beam is pointed to, or null if the beam is not shown + */ + Location getBeamTarget(); + + /** + * Sets the location that this end crystal is pointing to. Passing a null + * value will remove the current beam. + * + * @param location the location to point the beam to + * @throws IllegalArgumentException for differing worlds + */ + void setBeamTarget(Location location); +} diff --git a/src/main/java/org/bukkit/entity/EnderDragon.java b/src/main/java/org/bukkit/entity/EnderDragon.java new file mode 100644 index 00000000..4ea0e44e --- /dev/null +++ b/src/main/java/org/bukkit/entity/EnderDragon.java @@ -0,0 +1,78 @@ +package org.bukkit.entity; + +/** + * Represents an Ender Dragon + */ +public interface EnderDragon extends ComplexLivingEntity { + + /** + * Represents a phase or action that an Ender Dragon can perform. + */ + enum Phase { + /** + * The dragon will circle outside the ring of pillars if ender + * crystals remain or inside the ring if not. + */ + CIRCLING, + /** + * The dragon will fly towards a targeted player and shoot a + * fireball when within 64 blocks. + */ + STRAFING, + /** + * The dragon will fly towards the empty portal (approaching + * from the other side, if applicable). + */ + FLY_TO_PORTAL, + /** + * The dragon will land on on the portal. If the dragon is not near + * the portal, it will fly to it before mounting. + */ + LAND_ON_PORTAL, + /** + * The dragon will leave the portal. + */ + LEAVE_PORTAL, + /** + * The dragon will attack with dragon breath at its current location. + */ + BREATH_ATTACK, + /** + * The dragon will search for a player to attack with dragon breath. + * If no player is close enough to the dragon for 5 seconds, the + * dragon will charge at a player within 150 blocks or will take off + * and begin circling if no player is found. + */ + SEARCH_FOR_BREATH_ATTACK_TARGET, + /** + * The dragon will roar before performing a breath attack. + */ + ROAR_BEFORE_ATTACK, + /** + * The dragon will charge a player. + */ + CHARGE_PLAYER, + /** + * The dragon will fly to the vicinity of the portal and die. + */ + DYING, + /** + * The dragon will hover at its current location, not performing any actions. + */ + HOVER + } + + /** + * Gets the current phase that the dragon is performing. + * + * @return the current phase + */ + Phase getPhase(); + + /** + * Sets the next phase for the dragon to perform. + * + * @param phase the next phase + */ + void setPhase(Phase phase); +} diff --git a/src/main/java/org/bukkit/entity/EnderDragonPart.java b/src/main/java/org/bukkit/entity/EnderDragonPart.java new file mode 100644 index 00000000..9516f566 --- /dev/null +++ b/src/main/java/org/bukkit/entity/EnderDragonPart.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents an ender dragon part + */ +public interface EnderDragonPart extends ComplexEntityPart, Damageable { + public EnderDragon getParent(); +} diff --git a/src/main/java/org/bukkit/entity/EnderPearl.java b/src/main/java/org/bukkit/entity/EnderPearl.java new file mode 100644 index 00000000..db18a90b --- /dev/null +++ b/src/main/java/org/bukkit/entity/EnderPearl.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a thrown Ender Pearl entity + */ +public interface EnderPearl extends Projectile { + +} diff --git a/src/main/java/org/bukkit/entity/EnderSignal.java b/src/main/java/org/bukkit/entity/EnderSignal.java new file mode 100644 index 00000000..8e2723f8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/EnderSignal.java @@ -0,0 +1,62 @@ +package org.bukkit.entity; + +import org.bukkit.Location; + +/** + * Represents an EnderSignal, which is created upon throwing an ender eye. + */ +public interface EnderSignal extends Entity { + + /** + * Get the location this EnderSignal is moving towards. + * + * @return the {@link Location} this EnderSignal is moving towards. + */ + public Location getTargetLocation(); + + /** + * Set the {@link Location} this EnderSignal is moving towards. + *
    + * When setting a new target location, the {@link #getDropItem()} resets to + * a random value and the despawn timer gets set back to 0. + * + * @param location the new target location + */ + public void setTargetLocation(Location location); + + /** + * Gets if the EnderSignal should drop an item on death.
    + * If {@code true}, it will drop an item. If {@code false}, it will shatter. + * + * @return true if the EnderSignal will drop an item on death, or false if + * it will shatter + */ + public boolean getDropItem(); + + /** + * Sets if the EnderSignal should drop an item on death; or if it should + * shatter. + * + * @param drop true if the EnderSignal should drop an item on death, or + * false if it should shatter. + */ + public void setDropItem(boolean drop); + + /** + * Gets the amount of time this entity has been alive (in ticks). + *
    + * When this number is greater than 80, it will despawn on the next tick. + * + * @return the number of ticks this EnderSignal has been alive. + */ + public int getDespawnTimer(); + + /** + * Set how long this entity has been alive (in ticks). + *
    + * When this number is greater than 80, it will despawn on the next tick. + * + * @param timer how long (in ticks) this EnderSignal has been alive. + */ + public void setDespawnTimer(int timer); +} diff --git a/src/main/java/org/bukkit/entity/Enderman.java b/src/main/java/org/bukkit/entity/Enderman.java new file mode 100644 index 00000000..f537f697 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Enderman.java @@ -0,0 +1,23 @@ +package org.bukkit.entity; + +import org.bukkit.material.MaterialData; + +/** + * Represents an Enderman. + */ +public interface Enderman extends Monster { + + /** + * Get the id and data of the block that the Enderman is carrying. + * + * @return MaterialData containing the id and data of the block + */ + public MaterialData getCarriedMaterial(); + + /** + * Set the id and data of the block that the Enderman is carrying. + * + * @param material data to set the carried block to + */ + public void setCarriedMaterial(MaterialData material); +} diff --git a/src/main/java/org/bukkit/entity/Endermite.java b/src/main/java/org/bukkit/entity/Endermite.java new file mode 100644 index 00000000..dc1fa542 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Endermite.java @@ -0,0 +1,4 @@ +package org.bukkit.entity; + +public interface Endermite extends Monster { +} diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java new file mode 100644 index 00000000..f5d046de --- /dev/null +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -0,0 +1,500 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.bukkit.EntityEffect; +import org.bukkit.Nameable; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.metadata.Metadatable; +import org.bukkit.util.Vector; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.command.CommandSender; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +/** + * Represents a base entity in the world + */ +public interface Entity extends Metadatable, CommandSender, Nameable { + + /** + * Gets the entity's current position + * + * @return a new copy of Location containing the position of this entity + */ + public Location getLocation(); + + /** + * Stores the entity's current position in the provided Location object. + *

    + * If the provided Location is null this method does nothing and returns + * null. + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + public Location getLocation(Location loc); + + /** + * Sets this entity's velocity + * + * @param velocity New velocity to travel with + */ + public void setVelocity(Vector velocity); + + /** + * Gets this entity's current velocity + * + * @return Current traveling velocity of this entity + */ + public Vector getVelocity(); + + /** + * Gets the entity's height + * + * @return height of entity + */ + public double getHeight(); + + /** + * Gets the entity's width + * + * @return width of entity + */ + public double getWidth(); + + /** + * Returns true if the entity is supported by a block. This value is a + * state updated by the server and is not recalculated unless the entity + * moves. + * + * @return True if entity is on ground. + */ + public boolean isOnGround(); + + /** + * Gets the current world this entity resides in + * + * @return World + */ + public World getWorld(); + + /** + * Teleports this entity to the given location. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param location New location to teleport this entity to + * @return true if the teleport was successful + */ + public boolean teleport(Location location); + + /** + * Teleports this entity to the given location. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param location New location to teleport this entity to + * @param cause The cause of this teleportation + * @return true if the teleport was successful + */ + public boolean teleport(Location location, TeleportCause cause); + + /** + * Teleports this entity to the target Entity. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param destination Entity to teleport this entity to + * @return true if the teleport was successful + */ + public boolean teleport(Entity destination); + + /** + * Teleports this entity to the target Entity. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param destination Entity to teleport this entity to + * @param cause The cause of this teleportation + * @return true if the teleport was successful + */ + public boolean teleport(Entity destination, TeleportCause cause); + + /** + * Returns a list of entities within a bounding box centered around this + * entity + * + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @return {@code List} List of entities nearby + */ + public List getNearbyEntities(double x, double y, double z); + + /** + * Returns a unique id for this entity + * + * @return Entity id + */ + public int getEntityId(); + + /** + * Returns the entity's current fire ticks (ticks before the entity stops + * being on fire). + * + * @return int fireTicks + */ + public int getFireTicks(); + + /** + * Returns the entity's maximum fire ticks. + * + * @return int maxFireTicks + */ + public int getMaxFireTicks(); + + /** + * Sets the entity's current fire ticks (ticks before the entity stops + * being on fire). + * + * @param ticks Current ticks remaining + */ + public void setFireTicks(int ticks); + + /** + * Mark the entity's removal. + */ + public void remove(); + + /** + * Returns true if this entity has been marked for removal. + * + * @return True if it is dead. + */ + public boolean isDead(); + + /** + * Returns false if the entity has died or been despawned for some other + * reason. + * + * @return True if valid. + */ + public boolean isValid(); + + /** + * Gets the {@link Server} that contains this Entity + * + * @return Server instance running this Entity + */ + public Server getServer(); + + /** + * Gets the primary passenger of a vehicle. For vehicles that could have + * multiple passengers, this will only return the primary passenger. + * + * @return an entity + * @deprecated entities may have multiple passengers, use + * {@link #getPassengers()} + */ + @Deprecated + public Entity getPassenger(); + + /** + * Set the passenger of a vehicle. + * + * @param passenger The new passenger. + * @return false if it could not be done for whatever reason + * @deprecated entities may have multiple passengers, use + * {@link #getPassengers()} + */ + @Deprecated + public boolean setPassenger(Entity passenger); + + /** + * Gets a list of passengers of this vehicle. + *

    + * The returned list will not be directly linked to the entity's current + * passengers, and no guarantees are made as to its mutability. + * + * @return list of entities corresponding to current passengers. + */ + public List getPassengers(); + + /** + * Add a passenger to the vehicle. + * + * @param passenger The passenger to add + * @return false if it could not be done for whatever reason + */ + public boolean addPassenger(Entity passenger); + + /** + * Remove a passenger from the vehicle. + * + * @param passenger The passenger to remove + * @return false if it could not be done for whatever reason + */ + public boolean removePassenger(Entity passenger); + + /** + * Check if a vehicle has passengers. + * + * @return True if the vehicle has no passengers. + */ + public boolean isEmpty(); + + /** + * Eject any passenger. + * + * @return True if there was a passenger. + */ + public boolean eject(); + + /** + * Returns the distance this entity has fallen + * + * @return The distance. + */ + public float getFallDistance(); + + /** + * Sets the fall distance for this entity + * + * @param distance The new distance. + */ + public void setFallDistance(float distance); + + /** + * Record the last {@link EntityDamageEvent} inflicted on this entity + * + * @param event a {@link EntityDamageEvent} + */ + public void setLastDamageCause(EntityDamageEvent event); + + /** + * Retrieve the last {@link EntityDamageEvent} inflicted on this entity. + * This event may have been cancelled. + * + * @return the last known {@link EntityDamageEvent} or null if hitherto + * unharmed + */ + public EntityDamageEvent getLastDamageCause(); + + /** + * Returns a unique and persistent id for this entity + * + * @return unique id + */ + public UUID getUniqueId(); + + /** + * Gets the amount of ticks this entity has lived for. + *

    + * This is the equivalent to "age" in entities. + * + * @return Age of entity + */ + public int getTicksLived(); + + /** + * Sets the amount of ticks this entity has lived for. + *

    + * This is the equivalent to "age" in entities. May not be less than one + * tick. + * + * @param value Age of entity + */ + public void setTicksLived(int value); + + /** + * Performs the specified {@link EntityEffect} for this entity. + *

    + * This will be viewable to all players near the entity. + *

    + * If the effect is not applicable to this class of entity, it will not play. + * + * @param type Effect to play. + */ + public void playEffect(EntityEffect type); + + /** + * Get the type of the entity. + * + * @return The entity type. + */ + public EntityType getType(); + + /** + * Returns whether this entity is inside a vehicle. + * + * @return True if the entity is in a vehicle. + */ + public boolean isInsideVehicle(); + + /** + * Leave the current vehicle. If the entity is currently in a vehicle (and + * is removed from it), true will be returned, otherwise false will be + * returned. + * + * @return True if the entity was in a vehicle. + */ + public boolean leaveVehicle(); + + /** + * Get the vehicle that this player is inside. If there is no vehicle, + * null will be returned. + * + * @return The current vehicle. + */ + public Entity getVehicle(); + + /** + * Sets whether or not to display the mob's custom name client side. The + * name will be displayed above the mob similarly to a player. + *

    + * This value has no effect on players, they will always display their + * name. + * + * @param flag custom name or not + */ + public void setCustomNameVisible(boolean flag); + + /** + * Gets whether or not the mob's custom name is displayed client side. + *

    + * This value has no effect on players, they will always display their + * name. + * + * @return if the custom name is displayed + */ + public boolean isCustomNameVisible(); + + /** + * Sets whether the entity has a team colored (default: white) glow. + * + * @param flag if the entity is glowing + */ + void setGlowing(boolean flag); + + /** + * Gets whether the entity is glowing or not. + * + * @return whether the entity is glowing + */ + boolean isGlowing(); + + /** + * Sets whether the entity is invulnerable or not. + *

    + * When an entity is invulnerable it can only be damaged by players in + * creative mode. + * + * @param flag if the entity is invulnerable + */ + public void setInvulnerable(boolean flag); + + /** + * Gets whether the entity is invulnerable or not. + * + * @return whether the entity is + */ + public boolean isInvulnerable(); + + /** + * Gets whether the entity is silent or not. + * + * @return whether the entity is silent. + */ + public boolean isSilent(); + + /** + * Sets whether the entity is silent or not. + *

    + * When an entity is silent it will not produce any sound. + * + * @param flag if the entity is silent + */ + public void setSilent(boolean flag); + + /** + * Returns whether gravity applies to this entity. + * + * @return whether gravity applies + */ + boolean hasGravity(); + + /** + * Sets whether gravity applies to this entity. + * + * @param gravity whether gravity should apply + */ + void setGravity(boolean gravity); + + /** + * Gets the period of time (in ticks) before this entity can use a portal. + * + * @return portal cooldown ticks + */ + int getPortalCooldown(); + + /** + * Sets the period of time (in ticks) before this entity can use a portal. + * + * @param cooldown portal cooldown ticks + */ + void setPortalCooldown(int cooldown); + + /** + * Returns a set of tags for this entity. + *
    + * Entities can have no more than 1024 tags. + * + * @return a set of tags for this entity + */ + Set getScoreboardTags(); + + /** + * Add a tag to this entity. + *
    + * Entities can have no more than 1024 tags. + * + * @param tag the tag to add + * @return true if the tag was successfully added + */ + boolean addScoreboardTag(String tag); + + /** + * Removes a given tag from this entity. + * + * @param tag the tag to remove + * @return true if the tag was successfully removed + */ + boolean removeScoreboardTag(String tag); + + /** + * Returns the reaction of the entity when moved by a piston. + * + * @return reaction + */ + PistonMoveReaction getPistonMoveReaction(); + + // Spigot start + public class Spigot extends CommandSender.Spigot + { + + /** + * Returns whether this entity is invulnerable. + * + * @return True if the entity is invulnerable. + */ + public boolean isInvulnerable() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @Override + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java new file mode 100644 index 00000000..588e6048 --- /dev/null +++ b/src/main/java/org/bukkit/entity/EntityType.java @@ -0,0 +1,370 @@ +package org.bukkit.entity; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.SpawnerMinecart; +import org.bukkit.entity.minecart.RideableMinecart; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.entity.minecart.PoweredMinecart; +import org.bukkit.entity.minecart.StorageMinecart; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.potion.PotionEffectType; + +public enum EntityType { + + // These strings MUST match the strings in nms.EntityTypes and are case sensitive. + /** + * An item resting on the ground. + *

    + * Spawn with {@link World#dropItem(Location, ItemStack)} or {@link + * World#dropItemNaturally(Location, ItemStack)} + */ + DROPPED_ITEM("item", Item.class, 1, false), + /** + * An experience orb. + */ + EXPERIENCE_ORB("xp_orb", ExperienceOrb.class, 2), + /** + * @see AreaEffectCloud + */ + AREA_EFFECT_CLOUD("area_effect_cloud", AreaEffectCloud.class, 3), + /** + * @see ElderGuardian + */ + ELDER_GUARDIAN("elder_guardian", ElderGuardian.class, 4), + /** + * @see WitherSkeleton + */ + WITHER_SKELETON("wither_skeleton", WitherSkeleton.class, 5), + /** + * @see Stray + */ + STRAY("stray", Stray.class, 6), + /** + * A flying chicken egg. + */ + EGG("egg", Egg.class, 7), + /** + * A leash attached to a fencepost. + */ + LEASH_HITCH("leash_knot", LeashHitch.class, 8), + /** + * A painting on a wall. + */ + PAINTING("painting", Painting.class, 9), + /** + * An arrow projectile; may get stuck in the ground. + */ + ARROW("arrow", Arrow.class, 10), + /** + * A flying snowball. + */ + SNOWBALL("snowball", Snowball.class, 11), + /** + * A flying large fireball, as thrown by a Ghast for example. + */ + FIREBALL("fireball", LargeFireball.class, 12), + /** + * A flying small fireball, such as thrown by a Blaze or player. + */ + SMALL_FIREBALL("small_fireball", SmallFireball.class, 13), + /** + * A flying ender pearl. + */ + ENDER_PEARL("ender_pearl", EnderPearl.class, 14), + /** + * An ender eye signal. + */ + ENDER_SIGNAL("eye_of_ender_signal", EnderSignal.class, 15), + /** + * A flying splash potion. + */ + SPLASH_POTION("potion", SplashPotion.class, 16, false), + /** + * A flying experience bottle. + */ + THROWN_EXP_BOTTLE("xp_bottle", ThrownExpBottle.class, 17), + /** + * An item frame on a wall. + */ + ITEM_FRAME("item_frame", ItemFrame.class, 18), + /** + * A flying wither skull projectile. + */ + WITHER_SKULL("wither_skull", WitherSkull.class, 19), + /** + * Primed TNT that is about to explode. + */ + PRIMED_TNT("tnt", TNTPrimed.class, 20), + /** + * A block that is going to or is about to fall. + */ + FALLING_BLOCK("falling_block", FallingBlock.class, 21, false), + /** + * Internal representation of a Firework once it has been launched. + */ + FIREWORK("fireworks_rocket", Firework.class, 22, false), + /** + * @see Husk + */ + HUSK("husk", Husk.class, 23), + /** + * Like {@link #TIPPED_ARROW} but causes the {@link PotionEffectType#GLOWING} effect on all team members. + */ + SPECTRAL_ARROW("spectral_arrow", SpectralArrow.class, 24), + /** + * Bullet fired by {@link #SHULKER}. + */ + SHULKER_BULLET("shulker_bullet", ShulkerBullet.class, 25), + /** + * Like {@link #FIREBALL} but with added effects. + */ + DRAGON_FIREBALL("dragon_fireball", DragonFireball.class, 26), + /** + * @see ZombieVillager + */ + ZOMBIE_VILLAGER("zombie_villager", ZombieVillager.class, 27), + /** + * @see SkeletonHorse + */ + SKELETON_HORSE("skeleton_horse", SkeletonHorse.class, 28), + /** + * @see ZombieHorse + */ + ZOMBIE_HORSE("zombie_horse", ZombieHorse.class, 29), + /** + * Mechanical entity with an inventory for placing weapons / armor into. + */ + ARMOR_STAND("armor_stand", ArmorStand.class, 30), + /** + * @see Donkey + */ + DONKEY("donkey", Donkey.class, 31), + /** + * @see Mule + */ + MULE("mule", Mule.class, 32), + /** + * @see EvokerFangs + */ + EVOKER_FANGS("evocation_fangs", EvokerFangs.class, 33), + /** + * @see Evoker + */ + EVOKER("evocation_illager", Evoker.class, 34), + /** + * @see Vex + */ + VEX("vex", Vex.class, 35), + /** + * @see Vindicator + */ + VINDICATOR("vindication_illager", Vindicator.class, 36), + /** + * @see Illusioner + */ + ILLUSIONER("illusion_illager", Illusioner.class, 37), + /** + * @see CommandMinecart + */ + MINECART_COMMAND("commandblock_minecart", CommandMinecart.class, 40), + /** + * A placed boat. + */ + BOAT("boat", Boat.class, 41), + /** + * @see RideableMinecart + */ + MINECART("minecart", RideableMinecart.class, 42), + /** + * @see StorageMinecart + */ + MINECART_CHEST("chest_minecart", StorageMinecart.class, 43), + /** + * @see PoweredMinecart + */ + MINECART_FURNACE("furnace_minecart", PoweredMinecart.class, 44), + /** + * @see ExplosiveMinecart + */ + MINECART_TNT("tnt_minecart", ExplosiveMinecart.class, 45), + /** + * @see HopperMinecart + */ + MINECART_HOPPER("hopper_minecart", HopperMinecart.class, 46), + /** + * @see SpawnerMinecart + */ + MINECART_MOB_SPAWNER("spawner_minecart", SpawnerMinecart.class, 47), + CREEPER("creeper", Creeper.class, 50), + SKELETON("skeleton", Skeleton.class, 51), + SPIDER("spider", Spider.class, 52), + GIANT("giant", Giant.class, 53), + ZOMBIE("zombie", Zombie.class, 54), + SLIME("slime", Slime.class, 55), + GHAST("ghast", Ghast.class, 56), + PIG_ZOMBIE("zombie_pigman", PigZombie.class, 57), + ENDERMAN("enderman", Enderman.class, 58), + CAVE_SPIDER("cave_spider", CaveSpider.class, 59), + SILVERFISH("silverfish", Silverfish.class, 60), + BLAZE("blaze", Blaze.class, 61), + MAGMA_CUBE("magma_cube", MagmaCube.class, 62), + ENDER_DRAGON("ender_dragon", EnderDragon.class, 63), + WITHER("wither", Wither.class, 64), + BAT("bat", Bat.class, 65), + WITCH("witch", Witch.class, 66), + ENDERMITE("endermite", Endermite.class, 67), + GUARDIAN("guardian", Guardian.class, 68), + SHULKER("shulker", Shulker.class, 69), + PIG("pig", Pig.class, 90), + SHEEP("sheep", Sheep.class, 91), + COW("cow", Cow.class, 92), + CHICKEN("chicken", Chicken.class, 93), + SQUID("squid", Squid.class, 94), + WOLF("wolf", Wolf.class, 95), + MUSHROOM_COW("mooshroom", MushroomCow.class, 96), + SNOWMAN("snowman", Snowman.class, 97), + OCELOT("ocelot", Ocelot.class, 98), + IRON_GOLEM("villager_golem", IronGolem.class, 99), + HORSE("horse", Horse.class, 100), + RABBIT("rabbit", Rabbit.class, 101), + POLAR_BEAR("polar_bear", PolarBear.class, 102), + LLAMA("llama", Llama.class, 103), + LLAMA_SPIT("llama_spit", LlamaSpit.class, 104), + PARROT("parrot", Parrot.class, 105), + VILLAGER("villager", Villager.class, 120), + ENDER_CRYSTAL("ender_crystal", EnderCrystal.class, 200), + // These don't have an entity ID in nms.EntityTypes. + /** + * A flying lingering potion + */ + LINGERING_POTION(null, LingeringPotion.class, -1, false), + /** + * A fishing line and bobber. + */ + FISHING_HOOK(null, FishHook.class, -1, false), + /** + * A bolt of lightning. + *

    + * Spawn with {@link World#strikeLightning(Location)}. + */ + LIGHTNING(null, LightningStrike.class, -1, false), + WEATHER(null, Weather.class, -1, false), + PLAYER(null, Player.class, -1, false), + COMPLEX_PART(null, ComplexEntityPart.class, -1, false), + /** + * Like {@link #ARROW} but tipped with a specific potion which is applied on + * contact. + */ + TIPPED_ARROW(null, TippedArrow.class, -1), + /** + * An unknown entity without an Entity Class + */ + UNKNOWN(null, null, -1, false); + + private String name; + private Class clazz; + private short typeId; + private boolean independent, living; + + private static final Map NAME_MAP = new HashMap(); + private static final Map ID_MAP = new HashMap(); + + static { + for (EntityType type : values()) { + if (type.name != null) { + NAME_MAP.put(type.name.toLowerCase(java.util.Locale.ENGLISH), type); + } + if (type.typeId > 0) { + ID_MAP.put(type.typeId, type); + } + } + } + + private EntityType(String name, Class clazz, int typeId) { + this(name, clazz, typeId, true); + } + + private EntityType(String name, Class clazz, int typeId, boolean independent) { + this.name = name; + this.clazz = clazz; + this.typeId = (short) typeId; + this.independent = independent; + if (clazz != null) { + this.living = LivingEntity.class.isAssignableFrom(clazz); + } + } + + /** + * + * @return the entity type's name + * @deprecated Magic value + */ + @Deprecated + public String getName() { + return name; + } + + public Class getEntityClass() { + return clazz; + } + + /** + * + * @return the raw type id + * @deprecated Magic value + */ + @Deprecated + public short getTypeId() { + return typeId; + } + + /** + * + * @param name the entity type's name + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + public static EntityType fromName(String name) { + if (name == null) { + return null; + } + return NAME_MAP.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * + * @param id the raw type id + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + public static EntityType fromId(int id) { + if (id > Short.MAX_VALUE) { + return null; + } + return ID_MAP.get((short) id); + } + + /** + * Some entities cannot be spawned using {@link + * World#spawnEntity(Location, EntityType)} or {@link + * World#spawn(Location, Class)}, usually because they require additional + * information in order to spawn. + * + * @return False if the entity type cannot be spawned + */ + public boolean isSpawnable() { + return independent; + } + + public boolean isAlive() { + return living; + } +} diff --git a/src/main/java/org/bukkit/entity/Evoker.java b/src/main/java/org/bukkit/entity/Evoker.java new file mode 100644 index 00000000..7fd32bf4 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Evoker.java @@ -0,0 +1,63 @@ +package org.bukkit.entity; + +/** + * Represents an Evoker "Illager". + */ +public interface Evoker extends Spellcaster { + + /** + * Represents the current spell the Evoker is using. + * + * @deprecated future versions of Minecraft have additional spell casting + * entities. + */ + @Deprecated + public enum Spell { + + /** + * No spell is being evoked. + */ + NONE, + /** + * The spell that summons Vexes. + */ + SUMMON, + /** + * The spell that summons Fangs. + */ + FANGS, + /** + * The "wololo" spell. + */ + WOLOLO, + /** + * The spell that makes the casting entity invisible. + */ + DISAPPEAR, + /** + * The spell that makes the target blind. + */ + BLINDNESS; + } + + /** + * Gets the {@link Spell} the Evoker is currently using. + * + * @return the current spell + * @deprecated future versions of Minecraft have additional spell casting + * entities. + * + */ + @Deprecated + Spell getCurrentSpell(); + + /** + * Sets the {@link Spell} the Evoker is currently using. + * + * @param spell the spell the evoker should be using + * @deprecated future versions of Minecraft have additional spell casting + * entities. + */ + @Deprecated + void setCurrentSpell(Spell spell); +} diff --git a/src/main/java/org/bukkit/entity/EvokerFangs.java b/src/main/java/org/bukkit/entity/EvokerFangs.java new file mode 100644 index 00000000..6b6c1b97 --- /dev/null +++ b/src/main/java/org/bukkit/entity/EvokerFangs.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents Evoker Fangs. + */ +public interface EvokerFangs extends Entity { + + /** + * Gets the {@link LivingEntity} which summoned the fangs. + * + * @return the {@link LivingEntity} which summoned the fangs + */ + LivingEntity getOwner(); + + /** + * Sets the {@link LivingEntity} which summoned the fangs. + * + * @param owner the {@link LivingEntity} which summoned the fangs + */ + void setOwner(LivingEntity owner); +} diff --git a/src/main/java/org/bukkit/entity/ExperienceOrb.java b/src/main/java/org/bukkit/entity/ExperienceOrb.java new file mode 100644 index 00000000..c286edfd --- /dev/null +++ b/src/main/java/org/bukkit/entity/ExperienceOrb.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents an Experience Orb. + */ +public interface ExperienceOrb extends Entity { + + /** + * Gets how much experience is contained within this orb + * + * @return Amount of experience + */ + public int getExperience(); + + /** + * Sets how much experience is contained within this orb + * + * @param value Amount of experience + */ + public void setExperience(int value); +} diff --git a/src/main/java/org/bukkit/entity/Explosive.java b/src/main/java/org/bukkit/entity/Explosive.java new file mode 100644 index 00000000..48650f6a --- /dev/null +++ b/src/main/java/org/bukkit/entity/Explosive.java @@ -0,0 +1,35 @@ +package org.bukkit.entity; + +/** + * A representation of an explosive entity + */ +public interface Explosive extends Entity { + + /** + * Set the radius affected by this explosive's explosion + * + * @param yield The explosive yield + */ + public void setYield(float yield); + + /** + * Return the radius or yield of this explosive's explosion + * + * @return the radius of blocks affected + */ + public float getYield(); + + /** + * Set whether or not this explosive's explosion causes fire + * + * @param isIncendiary Whether it should cause fire + */ + public void setIsIncendiary(boolean isIncendiary); + + /** + * Return whether or not this explosive creates a fire when exploding + * + * @return true if the explosive creates fire, false otherwise + */ + public boolean isIncendiary(); +} diff --git a/src/main/java/org/bukkit/entity/FallingBlock.java b/src/main/java/org/bukkit/entity/FallingBlock.java new file mode 100644 index 00000000..bc56fa22 --- /dev/null +++ b/src/main/java/org/bukkit/entity/FallingBlock.java @@ -0,0 +1,62 @@ +package org.bukkit.entity; + +import org.bukkit.Material; + +/** + * Represents a falling block + */ +public interface FallingBlock extends Entity { + + /** + * Get the Material of the falling block + * + * @return Material of the block + */ + Material getMaterial(); + + /** + * Get the ID of the falling block + * + * @return ID type of the block + * @deprecated Magic value + */ + @Deprecated + int getBlockId(); + + /** + * Get the data for the falling block + * + * @return data of the block + * @deprecated Magic value + */ + @Deprecated + byte getBlockData(); + + /** + * Get if the falling block will break into an item if it cannot be placed + * + * @return true if the block will break into an item when obstructed + */ + boolean getDropItem(); + + /** + * Set if the falling block will break into an item if it cannot be placed + * + * @param drop true to break into an item when obstructed + */ + void setDropItem(boolean drop); + + /** + * Get the HurtEntities state of this block. + * + * @return whether entities will be damaged by this block. + */ + boolean canHurtEntities(); + + /** + * Set the HurtEntities state of this block. + * + * @param hurtEntities whether entities will be damaged by this block. + */ + void setHurtEntities(boolean hurtEntities); +} diff --git a/src/main/java/org/bukkit/entity/Fireball.java b/src/main/java/org/bukkit/entity/Fireball.java new file mode 100644 index 00000000..56ed5789 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Fireball.java @@ -0,0 +1,24 @@ +package org.bukkit.entity; + +import org.bukkit.util.Vector; + +/** + * Represents a Fireball. + */ +public interface Fireball extends Projectile, Explosive { + + /** + * Fireballs fly straight and do not take setVelocity(...) well. + * + * @param direction the direction this fireball is flying toward + */ + public void setDirection(Vector direction); + + /** + * Retrieve the direction this fireball is heading toward + * + * @return the direction + */ + public Vector getDirection(); + +} diff --git a/src/main/java/org/bukkit/entity/Firework.java b/src/main/java/org/bukkit/entity/Firework.java new file mode 100644 index 00000000..b8a8c075 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Firework.java @@ -0,0 +1,26 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.meta.FireworkMeta; + +public interface Firework extends Entity { + + /** + * Get a copy of the fireworks meta + * + * @return A copy of the current Firework meta + */ + FireworkMeta getFireworkMeta(); + + /** + * Apply the provided meta to the fireworks + * + * @param meta The FireworkMeta to apply + */ + void setFireworkMeta(FireworkMeta meta); + + /** + * Cause this firework to explode at earliest opportunity, as if it has no + * remaining fuse. + */ + void detonate(); +} diff --git a/src/main/java/org/bukkit/entity/Fish.java b/src/main/java/org/bukkit/entity/Fish.java new file mode 100644 index 00000000..12ed1ed7 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Fish.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a fishing hook. + * @deprecated in favor of {@link FishHook} + */ +public interface Fish extends FishHook { +} diff --git a/src/main/java/org/bukkit/entity/FishHook.java b/src/main/java/org/bukkit/entity/FishHook.java new file mode 100644 index 00000000..fdd48d0f --- /dev/null +++ b/src/main/java/org/bukkit/entity/FishHook.java @@ -0,0 +1,32 @@ +package org.bukkit.entity; + +/** + * Represents a fishing hook. + */ +public interface FishHook extends Projectile { + /** + * Gets the chance of a fish biting. + *

    + * 0.0 = No Chance.
    + * 1.0 = Instant catch. + * + * @return chance the bite chance + * @deprecated has no effect in newer Minecraft versions + */ + @Deprecated + public double getBiteChance(); + + /** + * Sets the chance of a fish biting. + *

    + * 0.0 = No Chance.
    + * 1.0 = Instant catch. + * + * @param chance the bite chance + * @throws IllegalArgumentException if the bite chance is not between 0 + * and 1 + * @deprecated has no effect in newer Minecraft versions + */ + @Deprecated + public void setBiteChance(double chance) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/entity/Flying.java b/src/main/java/org/bukkit/entity/Flying.java new file mode 100644 index 00000000..4f16a26c --- /dev/null +++ b/src/main/java/org/bukkit/entity/Flying.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Flying Entity. + */ +public interface Flying extends LivingEntity {} diff --git a/src/main/java/org/bukkit/entity/Ghast.java b/src/main/java/org/bukkit/entity/Ghast.java new file mode 100644 index 00000000..3f5edf76 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Ghast.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Ghast. + */ +public interface Ghast extends Flying {} diff --git a/src/main/java/org/bukkit/entity/Giant.java b/src/main/java/org/bukkit/entity/Giant.java new file mode 100644 index 00000000..610de577 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Giant.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Giant. + */ +public interface Giant extends Monster {} diff --git a/src/main/java/org/bukkit/entity/Golem.java b/src/main/java/org/bukkit/entity/Golem.java new file mode 100644 index 00000000..4165977e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Golem.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * A mechanical creature that may harm enemies. + */ +public interface Golem extends Creature { + +} diff --git a/src/main/java/org/bukkit/entity/Guardian.java b/src/main/java/org/bukkit/entity/Guardian.java new file mode 100644 index 00000000..98af0563 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Guardian.java @@ -0,0 +1,20 @@ +package org.bukkit.entity; + +public interface Guardian extends Monster { + + /** + * Check if the Guardian is an elder Guardian + * + * @return true if the Guardian is an Elder Guardian, false if not + * @deprecated should check if instance of {@link ElderGuardian}. + */ + @Deprecated + public boolean isElder(); + + /** + * @param shouldBeElder + * @deprecated Must spawn a new {@link ElderGuardian}. + */ + @Deprecated + public void setElder(boolean shouldBeElder); +} diff --git a/src/main/java/org/bukkit/entity/Hanging.java b/src/main/java/org/bukkit/entity/Hanging.java new file mode 100644 index 00000000..67e9b615 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Hanging.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +import org.bukkit.block.BlockFace; +import org.bukkit.material.Attachable; + +/** + * Represents a Hanging entity + */ +public interface Hanging extends Entity, Attachable { + + /** + * Sets the direction of the hanging entity, potentially overriding rules + * of placement. Note that if the result is not valid the object would + * normally drop as an item. + * + * @param face The new direction. + * @param force Whether to force it. + * @return False if force was false and there was no block for it to + * attach to in order to face the given direction. + */ + public boolean setFacingDirection(BlockFace face, boolean force); +} diff --git a/src/main/java/org/bukkit/entity/Horse.java b/src/main/java/org/bukkit/entity/Horse.java new file mode 100644 index 00000000..cfce8fa5 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Horse.java @@ -0,0 +1,162 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.HorseInventory; + +/** + * Represents a Horse. + */ +public interface Horse extends AbstractHorse { + + /** + * @deprecated different variants are differing classes + */ + @Deprecated + public enum Variant { + /** + * A normal horse + */ + HORSE, + /** + * A donkey + */ + DONKEY, + /** + * A mule + */ + MULE, + /** + * An undead horse + */ + UNDEAD_HORSE, + /** + * A skeleton horse + */ + SKELETON_HORSE, + /** + * Not really a horse :) + */ + LLAMA + ; + } + + /** + * Represents the base color that the horse has. + */ + public enum Color { + /** + * Snow white + */ + WHITE, + /** + * Very light brown + */ + CREAMY, + /** + * Chestnut + */ + CHESTNUT, + /** + * Light brown + */ + BROWN, + /** + * Pitch black + */ + BLACK, + /** + * Gray + */ + GRAY, + /** + * Dark brown + */ + DARK_BROWN, + ; + } + + /** + * Represents the style, or markings, that the horse has. + */ + public enum Style { + /** + * No markings + */ + NONE, + /** + * White socks or stripes + */ + WHITE, + /** + * Milky splotches + */ + WHITEFIELD, + /** + * Round white dots + */ + WHITE_DOTS, + /** + * Small black dots + */ + BLACK_DOTS, + ; + } + + /** + * Gets the horse's color. + *

    + * Colors only apply to horses, not to donkeys, mules, skeleton horses + * or undead horses. + * + * @return a {@link Color} representing the horse's group + */ + public Color getColor(); + + /** + * Sets the horse's color. + *

    + * Attempting to set a color for any donkey, mule, skeleton horse or + * undead horse will not result in a change. + * + * @param color a {@link Color} for this horse + */ + public void setColor(Color color); + + /** + * Gets the horse's style. + * Styles determine what kind of markings or patterns a horse has. + *

    + * Styles only apply to horses, not to donkeys, mules, skeleton horses + * or undead horses. + * + * @return a {@link Style} representing the horse's style + */ + public Style getStyle(); + + /** + * Sets the style of this horse. + * Styles determine what kind of markings or patterns a horse has. + *

    + * Attempting to set a style for any donkey, mule, skeleton horse or + * undead horse will not result in a change. + * + * @param style a {@link Style} for this horse + */ + public void setStyle(Style style); + + /** + * @return carrying chest status + * @deprecated see {@link ChestedHorse} + */ + @Deprecated + public boolean isCarryingChest(); + + /** + * @param chest + * @deprecated see {@link ChestedHorse} + */ + @Deprecated + public void setCarryingChest(boolean chest); + + @Override + public HorseInventory getInventory(); +} diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java new file mode 100644 index 00000000..518aa2a9 --- /dev/null +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -0,0 +1,320 @@ +package org.bukkit.entity; + +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.permissions.Permissible; + +/** + * Represents a human entity, such as an NPC or a player + */ +public interface HumanEntity extends LivingEntity, AnimalTamer, Permissible, InventoryHolder { + + /** + * Returns the name of this player + * + * @return Player name + */ + public String getName(); + + /** + * Get the player's inventory. + * + * @return The inventory of the player, this also contains the armor + * slots. + */ + public PlayerInventory getInventory(); + + /** + * Get the player's EnderChest inventory + * + * @return The EnderChest of the player + */ + public Inventory getEnderChest(); + + /** + * Gets the player's selected main hand + * + * @return the players main hand + */ + public MainHand getMainHand(); + + /** + * If the player currently has an inventory window open, this method will + * set a property of that window, such as the state of a progress bar. + * + * @param prop The property. + * @param value The value to set the property to. + * @return True if the property was successfully set. + */ + public boolean setWindowProperty(InventoryView.Property prop, int value); + + /** + * Gets the inventory view the player is currently viewing. If they do not + * have an inventory window open, it returns their internal crafting view. + * + * @return The inventory view. + */ + public InventoryView getOpenInventory(); + + /** + * Opens an inventory window with the specified inventory on the top and + * the player's inventory on the bottom. + * + * @param inventory The inventory to open + * @return The newly opened inventory view + */ + public InventoryView openInventory(Inventory inventory); + + /** + * Opens an empty workbench inventory window with the player's inventory + * on the bottom. + * + * @param location The location to attach it to. If null, the player's + * location is used. + * @param force If false, and there is no workbench block at the location, + * no inventory will be opened and null will be returned. + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + public InventoryView openWorkbench(Location location, boolean force); + + /** + * Opens an empty enchanting inventory window with the player's inventory + * on the bottom. + * + * @param location The location to attach it to. If null, the player's + * location is used. + * @param force If false, and there is no enchanting table at the + * location, no inventory will be opened and null will be returned. + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + public InventoryView openEnchanting(Location location, boolean force); + + /** + * Opens an inventory window to the specified inventory view. + * + * @param inventory The view to open + */ + public void openInventory(InventoryView inventory); + + /** + * Starts a trade between the player and the villager. + * + * Note that only one player may trade with a villager at once. You must use + * the force parameter for this. + * + * @param trader The merchant to trade with. Cannot be null. + * @param force whether to force the trade even if another player is trading + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + public InventoryView openMerchant(Villager trader, boolean force); + + /** + * Starts a trade between the player and the merchant. + * + * Note that only one player may trade with a merchant at once. You must use + * the force parameter for this. + * + * @param merchant The merchant to trade with. Cannot be null. + * @param force whether to force the trade even if another player is trading + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + public InventoryView openMerchant(Merchant merchant, boolean force); + + /** + * Force-closes the currently open inventory view for this player, if any. + */ + public void closeInventory(); + + /** + * Returns the ItemStack currently in your hand, can be empty. + * + * @return The ItemStack of the item you are currently holding. + * @deprecated Humans may now dual wield in their off hand, use explicit + * methods in {@link PlayerInventory}. + */ + @Deprecated + public ItemStack getItemInHand(); + + /** + * Sets the item to the given ItemStack, this will replace whatever the + * user was holding. + * + * @param item The ItemStack which will end up in the hand + * @deprecated Humans may now dual wield in their off hand, use explicit + * methods in {@link PlayerInventory}. + */ + @Deprecated + public void setItemInHand(ItemStack item); + + /** + * Returns the ItemStack currently on your cursor, can be empty. Will + * always be empty if the player currently has no open window. + * + * @return The ItemStack of the item you are currently moving around. + */ + public ItemStack getItemOnCursor(); + + /** + * Sets the item to the given ItemStack, this will replace whatever the + * user was moving. Will always be empty if the player currently has no + * open window. + * + * @param item The ItemStack which will end up in the hand + */ + public void setItemOnCursor(ItemStack item); + + /** + * Check whether a cooldown is active on the specified material. + * + * @param material the material to check + * @return if a cooldown is active on the material + */ + public boolean hasCooldown(Material material); + + /** + * Get the cooldown time in ticks remaining for the specified material. + * + * @param material the material to check + * @return the remaining cooldown time in ticks + */ + public int getCooldown(Material material); + + /** + * Set a cooldown on the specified material for a certain amount of ticks. + * ticks. 0 ticks will result in the removal of the cooldown. + *

    + * Cooldowns are used by the server for items such as ender pearls and + * shields to prevent them from being used repeatedly. + *

    + * Note that cooldowns will not by themselves stop an item from being used + * for attacking. + * + * @param material the material to set the cooldown for + * @param ticks the amount of ticks to set or 0 to remove + */ + public void setCooldown(Material material, int ticks); + + /** + * Returns whether this player is slumbering. + * + * @return slumber state + */ + public boolean isSleeping(); + + /** + * Get the sleep ticks of the player. This value may be capped. + * + * @return slumber ticks + */ + public int getSleepTicks(); + + /** + * Gets this human's current {@link GameMode} + * + * @return Current game mode + */ + public GameMode getGameMode(); + + /** + * Sets this human's current {@link GameMode} + * + * @param mode New game mode + */ + public void setGameMode(GameMode mode); + + /** + * Check if the player is currently blocking (ie with a shield). + * + * @return Whether they are blocking. + */ + public boolean isBlocking(); + + /** + * Check if the player currently has their hand raised (ie about to begin + * blocking). + * + * @return Whether their hand is raised + */ + public boolean isHandRaised(); + + /** + * Get the total amount of experience required for the player to level + * + * @return Experience required to level up + */ + public int getExpToLevel(); + + /** + * Gets the entity currently perched on the left shoulder or null if no + * entity. + *
    + * The returned entity will not be spawned within the world, so most + * operations are invalid unless the entity is first spawned in. + * + * @return left shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public Entity getShoulderEntityLeft(); + + /** + * Sets the entity currently perched on the left shoulder, or null to + * remove. This method will remove the entity from the world. + *
    + * Note that only a copy of the entity will be set to display on the + * shoulder. + *
    + * Also note that the client will currently only render {@link Parrot} + * entities. + * + * @param entity left shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public void setShoulderEntityLeft(Entity entity); + + /** + * Gets the entity currently perched on the right shoulder or null if no + * entity. + *
    + * The returned entity will not be spawned within the world, so most + * operations are invalid unless the entity is first spawned in. + * + * @return right shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public Entity getShoulderEntityRight(); + + /** + * Sets the entity currently perched on the right shoulder, or null to + * remove. This method will remove the entity from the world. + *
    + * Note that only a copy of the entity will be set to display on the + * shoulder. + *
    + * Also note that the client will currently only render {@link Parrot} + * entities. + * + * @param entity right shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public void setShoulderEntityRight(Entity entity); +} diff --git a/src/main/java/org/bukkit/entity/Husk.java b/src/main/java/org/bukkit/entity/Husk.java new file mode 100644 index 00000000..17139a2e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Husk.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Husk - variant of {@link Zombie}. + */ +public interface Husk extends Zombie { } diff --git a/src/main/java/org/bukkit/entity/Illager.java b/src/main/java/org/bukkit/entity/Illager.java new file mode 100644 index 00000000..b723fbac --- /dev/null +++ b/src/main/java/org/bukkit/entity/Illager.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a type of "Illager". + */ +public interface Illager extends Monster { } diff --git a/src/main/java/org/bukkit/entity/Illusioner.java b/src/main/java/org/bukkit/entity/Illusioner.java new file mode 100644 index 00000000..7c92c431 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Illusioner.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an Illusioner "Illager". + */ +public interface Illusioner extends Spellcaster { } diff --git a/src/main/java/org/bukkit/entity/IronGolem.java b/src/main/java/org/bukkit/entity/IronGolem.java new file mode 100644 index 00000000..655e37cb --- /dev/null +++ b/src/main/java/org/bukkit/entity/IronGolem.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * An iron Golem that protects Villages. + */ +public interface IronGolem extends Golem { + + /** + * Gets whether this iron golem was built by a player. + * + * @return Whether this iron golem was built by a player + */ + public boolean isPlayerCreated(); + + /** + * Sets whether this iron golem was built by a player or not. + * + * @param playerCreated true if you want to set the iron golem as being + * player created, false if you want it to be a natural village golem. + */ + public void setPlayerCreated(boolean playerCreated); +} diff --git a/src/main/java/org/bukkit/entity/Item.java b/src/main/java/org/bukkit/entity/Item.java new file mode 100644 index 00000000..90260b7e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Item.java @@ -0,0 +1,37 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.ItemStack; + +/** + * Represents an Item. + */ +public interface Item extends Entity { + + /** + * Gets the item stack associated with this item drop. + * + * @return An item stack. + */ + public ItemStack getItemStack(); + + /** + * Sets the item stack associated with this item drop. + * + * @param stack An item stack. + */ + public void setItemStack(ItemStack stack); + + /** + * Gets the delay before this Item is available to be picked up by players + * + * @return Remaining delay + */ + public int getPickupDelay(); + + /** + * Sets the delay before this Item is available to be picked up by players + * + * @param delay New delay + */ + public void setPickupDelay(int delay); +} diff --git a/src/main/java/org/bukkit/entity/ItemFrame.java b/src/main/java/org/bukkit/entity/ItemFrame.java new file mode 100644 index 00000000..8b86815d --- /dev/null +++ b/src/main/java/org/bukkit/entity/ItemFrame.java @@ -0,0 +1,39 @@ +package org.bukkit.entity; + +import org.bukkit.Rotation; +import org.bukkit.inventory.ItemStack; + +/** + * Represents an Item Frame + */ +public interface ItemFrame extends Hanging { + + /** + * Get the item in this frame + * + * @return a defensive copy the item in this item frame + */ + public ItemStack getItem(); + + /** + * Set the item in this frame + * + * @param item the new item + */ + public void setItem(ItemStack item); + + /** + * Get the rotation of the frame's item + * + * @return the direction + */ + public Rotation getRotation(); + + /** + * Set the rotation of the frame's item + * + * @param rotation the new rotation + * @throws IllegalArgumentException if rotation is null + */ + public void setRotation(Rotation rotation) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/entity/LargeFireball.java b/src/main/java/org/bukkit/entity/LargeFireball.java new file mode 100644 index 00000000..fc3a1093 --- /dev/null +++ b/src/main/java/org/bukkit/entity/LargeFireball.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a large {@link Fireball} + */ +public interface LargeFireball extends Fireball { +} diff --git a/src/main/java/org/bukkit/entity/LeashHitch.java b/src/main/java/org/bukkit/entity/LeashHitch.java new file mode 100644 index 00000000..9ac04c18 --- /dev/null +++ b/src/main/java/org/bukkit/entity/LeashHitch.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a Leash Hitch on a fence + */ +public interface LeashHitch extends Hanging { +} diff --git a/src/main/java/org/bukkit/entity/LightningStrike.java b/src/main/java/org/bukkit/entity/LightningStrike.java new file mode 100644 index 00000000..aa809392 --- /dev/null +++ b/src/main/java/org/bukkit/entity/LightningStrike.java @@ -0,0 +1,33 @@ +package org.bukkit.entity; + +/** + * Represents an instance of a lightning strike. May or may not do damage. + */ +public interface LightningStrike extends Weather { + + /** + * Returns whether the strike is an effect that does no damage. + * + * @return whether the strike is an effect + */ + public boolean isEffect(); + + // Spigot start + public class Spigot extends Entity.Spigot + { + + /* + * Returns whether the strike is silent. + * + * @return whether the strike is silent. + */ + public boolean isSilent() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @Override + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/entity/LingeringPotion.java b/src/main/java/org/bukkit/entity/LingeringPotion.java new file mode 100644 index 00000000..5aca7cb8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/LingeringPotion.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown lingering potion bottle + */ +public interface LingeringPotion extends ThrownPotion { } diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java new file mode 100644 index 00000000..644aebbc --- /dev/null +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -0,0 +1,362 @@ +package org.bukkit.entity; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.attribute.Attributable; +import org.bukkit.block.Block; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; + +/** + * Represents a living entity, such as a monster or player + */ +public interface LivingEntity extends Attributable, Entity, Damageable, ProjectileSource { + + /** + * Gets the height of the living entity's eyes above its Location. + * + * @return height of the living entity's eyes above its location + */ + public double getEyeHeight(); + + /** + * Gets the height of the living entity's eyes above its Location. + * + * @param ignorePose if set to true, the effects of pose changes, eg + * sneaking and gliding will be ignored + * @return height of the living entity's eyes above its location + */ + public double getEyeHeight(boolean ignorePose); + + /** + * Get a Location detailing the current eye position of the living entity. + * + * @return a location at the eyes of the living entity + */ + public Location getEyeLocation(); + + /** + * Gets all blocks along the living entity's line of sight. + *

    + * This list contains all blocks from the living entity's eye position to + * target inclusive. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan (may be limited + * by server by at least 100 blocks, no less) + * @return list containing all blocks along the living entity's line of + * sight + */ + public List getLineOfSight(Set transparent, int maxDistance); + + /** + * Gets the block that the living entity has targeted. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan (may be limited + * by server by at least 100 blocks, no less) + * @return block that the living entity has targeted + */ + public Block getTargetBlock(Set transparent, int maxDistance); + + /** + * Gets the last two blocks along the living entity's line of sight. + *

    + * The target block will be the last block in the list. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan. This may be + * further limited by the server, but never to less than 100 blocks + * @return list containing the last 2 blocks along the living entity's + * line of sight + */ + public List getLastTwoTargetBlocks(Set transparent, int maxDistance); + + /** + * Returns the amount of air that the living entity has remaining, in + * ticks. + * + * @return amount of air remaining + */ + public int getRemainingAir(); + + /** + * Sets the amount of air that the living entity has remaining, in ticks. + * + * @param ticks amount of air remaining + */ + public void setRemainingAir(int ticks); + + /** + * Returns the maximum amount of air the living entity can have, in ticks. + * + * @return maximum amount of air + */ + public int getMaximumAir(); + + /** + * Sets the maximum amount of air the living entity can have, in ticks. + * + * @param ticks maximum amount of air + */ + public void setMaximumAir(int ticks); + + /** + * Returns the living entity's current maximum no damage ticks. + *

    + * This is the maximum duration in which the living entity will not take + * damage. + * + * @return maximum no damage ticks + */ + public int getMaximumNoDamageTicks(); + + /** + * Sets the living entity's current maximum no damage ticks. + * + * @param ticks maximum amount of no damage ticks + */ + public void setMaximumNoDamageTicks(int ticks); + + /** + * Returns the living entity's last damage taken in the current no damage + * ticks time. + *

    + * Only damage higher than this amount will further damage the living + * entity. + * + * @return damage taken since the last no damage ticks time period + */ + public double getLastDamage(); + + /** + * Sets the damage dealt within the current no damage ticks time period. + * + * @param damage amount of damage + */ + public void setLastDamage(double damage); + + /** + * Returns the living entity's current no damage ticks. + * + * @return amount of no damage ticks + */ + public int getNoDamageTicks(); + + /** + * Sets the living entity's current no damage ticks. + * + * @param ticks amount of no damage ticks + */ + public void setNoDamageTicks(int ticks); + + /** + * Gets the player identified as the killer of the living entity. + *

    + * May be null. + * + * @return killer player, or null if none found + */ + public Player getKiller(); + + /** + * Adds the given {@link PotionEffect} to the living entity. + *

    + * Only one potion effect can be present for a given {@link + * PotionEffectType}. + * + * @param effect PotionEffect to be added + * @return whether the effect could be added + */ + public boolean addPotionEffect(PotionEffect effect); + + /** + * Adds the given {@link PotionEffect} to the living entity. + *

    + * Only one potion effect can be present for a given {@link + * PotionEffectType}. + * + * @param effect PotionEffect to be added + * @param force whether conflicting effects should be removed + * @return whether the effect could be added + */ + public boolean addPotionEffect(PotionEffect effect, boolean force); + + /** + * Attempts to add all of the given {@link PotionEffect} to the living + * entity. + * + * @param effects the effects to add + * @return whether all of the effects could be added + */ + public boolean addPotionEffects(Collection effects); + + /** + * Returns whether the living entity already has an existing effect of + * the given {@link PotionEffectType} applied to it. + * + * @param type the potion type to check + * @return whether the living entity has this potion effect active on them + */ + public boolean hasPotionEffect(PotionEffectType type); + + /** + * Returns the active {@link PotionEffect} of the specified type. + *

    + * If the effect is not present on the entity then null will be returned. + * + * @param type the potion type to check + * @return the effect active on this entity, or null if not active. + */ + public PotionEffect getPotionEffect(PotionEffectType type); + + /** + * Removes any effects present of the given {@link PotionEffectType}. + * + * @param type the potion type to remove + */ + public void removePotionEffect(PotionEffectType type); + + /** + * Returns all currently active {@link PotionEffect}s on the living + * entity. + * + * @return a collection of {@link PotionEffect}s + */ + public Collection getActivePotionEffects(); + + /** + * Checks whether the living entity has block line of sight to another. + *

    + * This uses the same algorithm that hostile mobs use to find the closest + * player. + * + * @param other the entity to determine line of sight to + * @return true if there is a line of sight, false if not + */ + public boolean hasLineOfSight(Entity other); + + /** + * Returns if the living entity despawns when away from players or not. + *

    + * By default, animals are not removed while other mobs are. + * + * @return true if the living entity is removed when away from players + */ + public boolean getRemoveWhenFarAway(); + + /** + * Sets whether or not the living entity despawns when away from players + * or not. + * + * @param remove the removal status + */ + public void setRemoveWhenFarAway(boolean remove); + + /** + * Gets the inventory with the equipment worn by the living entity. + * + * @return the living entity's inventory + */ + public EntityEquipment getEquipment(); + + /** + * Sets whether or not the living entity can pick up items. + * + * @param pickup whether or not the living entity can pick up items + */ + public void setCanPickupItems(boolean pickup); + + /** + * Gets if the living entity can pick up items. + * + * @return whether or not the living entity can pick up items + */ + public boolean getCanPickupItems(); + + /** + * Returns whether the entity is currently leashed. + * + * @return whether the entity is leashed + */ + public boolean isLeashed(); + + /** + * Gets the entity that is currently leading this entity. + * + * @return the entity holding the leash + * @throws IllegalStateException if not currently leashed + */ + public Entity getLeashHolder() throws IllegalStateException; + + /** + * Sets the leash on this entity to be held by the supplied entity. + *

    + * This method has no effect on EnderDragons, Withers, Players, or Bats. + * Non-living entities excluding leashes will not persist as leash + * holders. + * + * @param holder the entity to leash this entity to + * @return whether the operation was successful + */ + public boolean setLeashHolder(Entity holder); + + /** + * Checks to see if an entity is gliding, such as using an Elytra. + * @return True if this entity is gliding. + */ + public boolean isGliding(); + + /** + * Makes entity start or stop gliding. This will work even if an Elytra + * is not equipped, but will be reverted by the server immediately after + * unless an event-cancelling mechanism is put in place. + * @param gliding True if the entity is gliding. + */ + public void setGliding(boolean gliding); + + /** + * Sets whether an entity will have AI. + * + * @param ai whether the mob will have AI or not. + */ + void setAI(boolean ai); + + /** + * Checks whether an entity has AI. + * + * @return true if the entity has AI, otherwise false. + */ + boolean hasAI(); + + /** + * Set if this entity will be subject to collisions other entities. + *

    + * Note that collisions are bidirectional, so this method would need to be + * set to false on both the collidee and the collidant to ensure no + * collisions take place. + * + * @param collidable collision status + */ + void setCollidable(boolean collidable); + + /** + * Gets if this entity is subject to collisions with other entities. + *

    + * Please note that this method returns only the custom collidable state, + * not whether the entity is non-collidable for other reasons such as being + * dead. + * + * @return collision status + */ + boolean isCollidable(); +} diff --git a/src/main/java/org/bukkit/entity/Llama.java b/src/main/java/org/bukkit/entity/Llama.java new file mode 100644 index 00000000..9422d56c --- /dev/null +++ b/src/main/java/org/bukkit/entity/Llama.java @@ -0,0 +1,66 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.LlamaInventory; + +/** + * Represents a Llama. + */ +public interface Llama extends ChestedHorse { + + /** + * Represents the base color that the llama has. + */ + public enum Color { + + /** + * A cream-colored llama. + */ + CREAMY, + /** + * A white llama. + */ + WHITE, + /** + * A brown llama. + */ + BROWN, + /** + * A gray llama. + */ + GRAY; + } + + /** + * Gets the llama's color. + * + * @return a {@link Color} representing the llama's color + */ + Color getColor(); + + /** + * Sets the llama's color. + * + * @param color a {@link Color} for this llama + */ + void setColor(Color color); + + /** + * Gets the llama's strength. A higher strength llama will have more + * inventory slots and be more threatening to entities. + * + * @return llama strength [1,5] + */ + int getStrength(); + + /** + * Sets the llama's strength. A higher strength llama will have more + * inventory slots and be more threatening to entities. Inventory slots are + * equal to strength * 3. + * + * @param strength llama strength [1,5] + */ + void setStrength(int strength); + + @Override + LlamaInventory getInventory(); +} diff --git a/src/main/java/org/bukkit/entity/LlamaSpit.java b/src/main/java/org/bukkit/entity/LlamaSpit.java new file mode 100644 index 00000000..9890dffe --- /dev/null +++ b/src/main/java/org/bukkit/entity/LlamaSpit.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents Llama spit. + */ +public interface LlamaSpit extends Projectile { } diff --git a/src/main/java/org/bukkit/entity/MagmaCube.java b/src/main/java/org/bukkit/entity/MagmaCube.java new file mode 100644 index 00000000..714b4426 --- /dev/null +++ b/src/main/java/org/bukkit/entity/MagmaCube.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a MagmaCube. + */ +public interface MagmaCube extends Slime { +} diff --git a/src/main/java/org/bukkit/entity/Minecart.java b/src/main/java/org/bukkit/entity/Minecart.java new file mode 100644 index 00000000..9007491f --- /dev/null +++ b/src/main/java/org/bukkit/entity/Minecart.java @@ -0,0 +1,123 @@ +package org.bukkit.entity; + +import org.bukkit.material.MaterialData; +import org.bukkit.util.Vector; + +/** + * Represents a minecart entity. + */ +public interface Minecart extends Vehicle { + + /** + * Sets a minecart's damage. + * + * @param damage over 40 to "kill" a minecart + */ + public void setDamage(double damage); + + /** + * Gets a minecart's damage. + * + * @return The damage + */ + public double getDamage(); + + /** + * Gets the maximum speed of a minecart. The speed is unrelated to the + * velocity. + * + * @return The max speed + */ + public double getMaxSpeed(); + + /** + * Sets the maximum speed of a minecart. Must be nonnegative. Default is + * 0.4D. + * + * @param speed The max speed + */ + public void setMaxSpeed(double speed); + + /** + * Returns whether this minecart will slow down faster without a passenger + * occupying it + * + * @return Whether it decelerates faster + */ + public boolean isSlowWhenEmpty(); + + /** + * Sets whether this minecart will slow down faster without a passenger + * occupying it + * + * @param slow Whether it will decelerate faster + */ + public void setSlowWhenEmpty(boolean slow); + + /** + * Gets the flying velocity modifier. Used for minecarts that are in + * mid-air. A flying minecart's velocity is multiplied by this factor each + * tick. + * + * @return The vector factor + */ + public Vector getFlyingVelocityMod(); + + /** + * Sets the flying velocity modifier. Used for minecarts that are in + * mid-air. A flying minecart's velocity is multiplied by this factor each + * tick. + * + * @param flying velocity modifier vector + */ + public void setFlyingVelocityMod(Vector flying); + + /** + * Gets the derailed velocity modifier. Used for minecarts that are on the + * ground, but not on rails. + *

    + * A derailed minecart's velocity is multiplied by this factor each tick. + * + * @return derailed visible speed + */ + public Vector getDerailedVelocityMod(); + + /** + * Sets the derailed velocity modifier. Used for minecarts that are on the + * ground, but not on rails. A derailed minecart's velocity is multiplied + * by this factor each tick. + * + * @param derailed visible speed + */ + public void setDerailedVelocityMod(Vector derailed); + + /** + * Sets the display block for this minecart. + * Passing a null value will set the minecart to have no display block. + * + * @param material the material to set as display block. + */ + public void setDisplayBlock(MaterialData material); + + /** + * Gets the display block for this minecart. + * This function will return the type AIR if none is set. + * + * @return the block displayed by this minecart. + */ + public MaterialData getDisplayBlock(); + + /** + * Sets the offset of the display block. + * + * @param offset the block offset to set for this minecart. + */ + public void setDisplayBlockOffset(int offset); + + /** + * Gets the offset of the display block. + * + * @return the current block offset for this minecart. + */ + public int getDisplayBlockOffset(); +} diff --git a/src/main/java/org/bukkit/entity/Monster.java b/src/main/java/org/bukkit/entity/Monster.java new file mode 100644 index 00000000..fce2efd8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Monster.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Monster. + */ +public interface Monster extends Creature {} diff --git a/src/main/java/org/bukkit/entity/Mule.java b/src/main/java/org/bukkit/entity/Mule.java new file mode 100644 index 00000000..4f5efb36 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Mule.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Mule - variant of {@link ChestedHorse}. + */ +public interface Mule extends ChestedHorse { } diff --git a/src/main/java/org/bukkit/entity/MushroomCow.java b/src/main/java/org/bukkit/entity/MushroomCow.java new file mode 100644 index 00000000..84154de1 --- /dev/null +++ b/src/main/java/org/bukkit/entity/MushroomCow.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a mushroom {@link Cow} + */ +public interface MushroomCow extends Cow { + +} diff --git a/src/main/java/org/bukkit/entity/NPC.java b/src/main/java/org/bukkit/entity/NPC.java new file mode 100644 index 00000000..0c6b175b --- /dev/null +++ b/src/main/java/org/bukkit/entity/NPC.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a non-player character + */ +public interface NPC extends Creature { + +} diff --git a/src/main/java/org/bukkit/entity/Ocelot.java b/src/main/java/org/bukkit/entity/Ocelot.java new file mode 100644 index 00000000..0005970a --- /dev/null +++ b/src/main/java/org/bukkit/entity/Ocelot.java @@ -0,0 +1,68 @@ + +package org.bukkit.entity; + +/** + * A wild tameable cat + */ +public interface Ocelot extends Animals, Tameable, Sittable { + + /** + * Gets the current type of this cat. + * + * @return Type of the cat. + */ + public Type getCatType(); + + /** + * Sets the current type of this cat. + * + * @param type New type of this cat. + */ + public void setCatType(Type type); + + /** + * Represents the various different cat types there are. + */ + public enum Type { + WILD_OCELOT(0), + BLACK_CAT(1), + RED_CAT(2), + SIAMESE_CAT(3); + + private static final Type[] types = new Type[Type.values().length]; + private final int id; + + static { + for (Type type : values()) { + types[type.getId()] = type; + } + } + + private Type(int id) { + this.id = id; + } + + /** + * Gets the ID of this cat type. + * + * @return Type ID. + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Gets a cat type by its ID. + * + * @param id ID of the cat type to get. + * @return Resulting type, or null if not found. + * @deprecated Magic value + */ + @Deprecated + public static Type getType(int id) { + return (id >= types.length) ? null : types[id]; + } + } +} diff --git a/src/main/java/org/bukkit/entity/Painting.java b/src/main/java/org/bukkit/entity/Painting.java new file mode 100644 index 00000000..6afa117e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Painting.java @@ -0,0 +1,39 @@ +package org.bukkit.entity; + +import org.bukkit.Art; +import org.bukkit.event.hanging.HangingBreakEvent; + +/** + * Represents a Painting. + */ +public interface Painting extends Hanging { + + /** + * Get the art on this painting + * + * @return The art + */ + public Art getArt(); + + /** + * Set the art on this painting + * + * @param art The new art + * @return False if the new art won't fit at the painting's current + * location + */ + public boolean setArt(Art art); + + /** + * Set the art on this painting + * + * @param art The new art + * @param force If true, force the new art regardless of whether it fits + * at the current location. Note that forcing it where it can't fit + * normally causes it to drop as an item unless you override this by + * catching the {@link HangingBreakEvent}. + * @return False if force was false and the new art won't fit at the + * painting's current location + */ + public boolean setArt(Art art, boolean force); +} diff --git a/src/main/java/org/bukkit/entity/Parrot.java b/src/main/java/org/bukkit/entity/Parrot.java new file mode 100644 index 00000000..ccd3d1ff --- /dev/null +++ b/src/main/java/org/bukkit/entity/Parrot.java @@ -0,0 +1,47 @@ +package org.bukkit.entity; + +/** + * Represents a Parrot. + */ +public interface Parrot extends Animals, Tameable, Sittable { + + /** + * Get the variant of this parrot. + * + * @return parrot variant + */ + public Variant getVariant(); + + /** + * Set the variant of this parrot. + * + * @param variant parrot variant + */ + public void setVariant(Variant variant); + + /** + * Represents the variant of a parrot - ie its color. + */ + public enum Variant { + /** + * Classic parrot - red with colored wingtips. + */ + RED, + /** + * Royal blue colored parrot. + */ + BLUE, + /** + * Green colored parrot. + */ + GREEN, + /** + * Cyan colored parrot. + */ + CYAN, + /** + * Gray colored parrot. + */ + GRAY; + } +} diff --git a/src/main/java/org/bukkit/entity/Pig.java b/src/main/java/org/bukkit/entity/Pig.java new file mode 100644 index 00000000..28f59f2c --- /dev/null +++ b/src/main/java/org/bukkit/entity/Pig.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents a Pig. + */ +public interface Pig extends Animals, Vehicle { + + /** + * Check if the pig has a saddle. + * + * @return if the pig has been saddled. + */ + public boolean hasSaddle(); + + /** + * Sets if the pig has a saddle or not + * + * @param saddled set if the pig has a saddle or not. + */ + public void setSaddle(boolean saddled); +} diff --git a/src/main/java/org/bukkit/entity/PigZombie.java b/src/main/java/org/bukkit/entity/PigZombie.java new file mode 100644 index 00000000..2f086728 --- /dev/null +++ b/src/main/java/org/bukkit/entity/PigZombie.java @@ -0,0 +1,36 @@ +package org.bukkit.entity; + +/** + * Represents a Pig Zombie. + */ +public interface PigZombie extends Zombie { + + /** + * Get the pig zombie's current anger level. + * + * @return The anger level. + */ + int getAnger(); + + /** + * Set the pig zombie's current anger level. + * + * @param level The anger level. Higher levels of anger take longer to + * wear off. + */ + void setAnger(int level); + + /** + * Shorthand; sets to either 0 or the default level. + * + * @param angry Whether the zombie should be angry. + */ + void setAngry(boolean angry); + + /** + * Shorthand; gets whether the zombie is angry. + * + * @return True if the zombie is angry, otherwise false. + */ + boolean isAngry(); +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java new file mode 100644 index 00000000..81dc8147 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Player.java @@ -0,0 +1,1564 @@ +package org.bukkit.entity; + +import java.net.InetSocketAddress; + +import org.bukkit.Achievement; +import org.bukkit.ChatColor; +import org.bukkit.Effect; +import org.bukkit.GameMode; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Note; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.Statistic; +import org.bukkit.WeatherType; +import org.bukkit.advancement.Advancement; +import org.bukkit.advancement.AdvancementProgress; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.Conversable; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; +import org.bukkit.map.MapView; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.scoreboard.Scoreboard; + +/** + * Represents a player, connected or not + */ +public interface Player extends HumanEntity, Conversable, CommandSender, OfflinePlayer, PluginMessageRecipient { + + /** + * Gets the "friendly" name to display of this player. This may include + * color. + *

    + * Note that this name will not be displayed in game, only in chat and + * places defined by plugins. + * + * @return the friendly name + */ + public String getDisplayName(); + + /** + * Sets the "friendly" name to display of this player. This may include + * color. + *

    + * Note that this name will not be displayed in game, only in chat and + * places defined by plugins. + * + * @param name The new display name. + */ + public void setDisplayName(String name); + + /** + * Gets the name that is shown on the player list. + * + * @return the player list name + */ + public String getPlayerListName(); + + /** + * Sets the name that is shown on the in-game player list. + *

    + * The name cannot be longer than 16 characters, but {@link ChatColor} is + * supported. + *

    + * If the value is null, the name will be identical to {@link #getName()}. + *

    + * This name is case sensitive and unique, two names with different casing + * will appear as two different people. If a player joins afterwards with + * a name that conflicts with a player's custom list name, the joining + * player's player list name will have a random number appended to it (1-2 + * characters long in the default implementation). If the joining player's + * name is 15 or 16 characters long, part of the name will be truncated at + * the end to allow the addition of the two digits. + * + * @param name new player list name + * @throws IllegalArgumentException if the name is already used by someone + * else + * @throws IllegalArgumentException if the length of the name is too long + */ + public void setPlayerListName(String name); + + /** + * Set the target of the player's compass. + * + * @param loc Location to point to + */ + public void setCompassTarget(Location loc); + + /** + * Get the previously set compass target. + * + * @return location of the target + */ + public Location getCompassTarget(); + + /** + * Gets the socket address of this player + * + * @return the player's address + */ + public InetSocketAddress getAddress(); + + /** + * Sends this sender a message raw + * + * @param message Message to be displayed + */ + public void sendRawMessage(String message); + + /** + * Kicks player with custom kick message. + * + * @param message kick message + */ + public void kickPlayer(String message); + + /** + * Says a message (or runs a command). + * + * @param msg message to print + */ + public void chat(String msg); + + /** + * Makes the player perform the given command + * + * @param command Command to perform + * @return true if the command was successful, otherwise false + */ + public boolean performCommand(String command); + + /** + * Returns if the player is in sneak mode + * + * @return true if player is in sneak mode + */ + public boolean isSneaking(); + + /** + * Sets the sneak mode the player + * + * @param sneak true if player should appear sneaking + */ + public void setSneaking(boolean sneak); + + /** + * Gets whether the player is sprinting or not. + * + * @return true if player is sprinting. + */ + public boolean isSprinting(); + + /** + * Sets whether the player is sprinting or not. + * + * @param sprinting true if the player should be sprinting + */ + public void setSprinting(boolean sprinting); + + /** + * Saves the players current location, health, inventory, motion, and + * other information into the username.dat file, in the world/player + * folder + */ + public void saveData(); + + /** + * Loads the players current location, health, inventory, motion, and + * other information from the username.dat file, in the world/player + * folder. + *

    + * Note: This will overwrite the players current inventory, health, + * motion, etc, with the state from the saved dat file. + */ + public void loadData(); + + /** + * Sets whether the player is ignored as not sleeping. If everyone is + * either sleeping or has this flag set, then time will advance to the + * next day. If everyone has this flag set but no one is actually in bed, + * then nothing will happen. + * + * @param isSleeping Whether to ignore. + */ + public void setSleepingIgnored(boolean isSleeping); + + /** + * Returns whether the player is sleeping ignored. + * + * @return Whether player is ignoring sleep. + */ + public boolean isSleepingIgnored(); + + /** + * Play a note for a player at a location. This requires a note block + * at the particular location (as far as the client is concerned). This + * will not work without a note block. This will not work with cake. + * + * @param loc The location of a note block. + * @param instrument The instrument ID. + * @param note The note ID. + * @deprecated Magic value + */ + @Deprecated + public void playNote(Location loc, byte instrument, byte note); + + /** + * Play a note for a player at a location. This requires a note block + * at the particular location (as far as the client is concerned). This + * will not work without a note block. This will not work with cake. + * + * @param loc The location of a note block + * @param instrument The instrument + * @param note The note + */ + public void playNote(Location loc, Instrument instrument, Note note); + + + /** + * Play a sound for a player at the location. + *

    + * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + public void playSound(Location location, Sound sound, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

    + * This function will fail silently if Location or Sound are null. No + * sound will be heard by the player if their client does not have the + * respective sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + public void playSound(Location location, String sound, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

    + * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param category The category of the sound + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

    + * This function will fail silently if Location or Sound are null. No sound + * will be heard by the player if their client does not have the respective + * sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param category The category of the sound + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + */ + public void stopSound(Sound sound); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + */ + public void stopSound(String sound); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + * @param category the category of the sound + */ + public void stopSound(Sound sound, SoundCategory category); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + * @param category the category of the sound + */ + public void stopSound(String sound, SoundCategory category); + + /** + * Plays an effect to just this player. + * + * @param loc the location to play the effect at + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @deprecated Magic value + */ + @Deprecated + public void playEffect(Location loc, Effect effect, int data); + + /** + * Plays an effect to just this player. + * + * @param the data based based on the type of the effect + * @param loc the location to play the effect at + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(Location loc, Effect effect, T data); + + /** + * Send a block change. This fakes a block change packet for a user at a + * certain location. This will not actually change the world in any way. + * + * @param loc The location of the changed block + * @param material The new block + * @param data The block data + * @deprecated Magic value + */ + @Deprecated + public void sendBlockChange(Location loc, Material material, byte data); + + /** + * Send a chunk change. This fakes a chunk change packet for a user at a + * certain location. The updated cuboid must be entirely within a single + * chunk. This will not actually change the world in any way. + *

    + * At least one of the dimensions of the cuboid must be even. The size of + * the data buffer must be 2.5*sx*sy*sz and formatted in accordance with + * the Packet51 format. + * + * @param loc The location of the cuboid + * @param sx The x size of the cuboid + * @param sy The y size of the cuboid + * @param sz The z size of the cuboid + * @param data The data to be sent + * @return true if the chunk change packet was sent + * @deprecated Magic value + */ + @Deprecated + public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data); + + /** + * Send a block change. This fakes a block change packet for a user at a + * certain location. This will not actually change the world in any way. + * + * @param loc The location of the changed block + * @param material The new block ID + * @param data The block data + * @deprecated Magic value + */ + @Deprecated + public void sendBlockChange(Location loc, int material, byte data); + + /** + * Send a sign change. This fakes a sign change packet for a user at + * a certain location. This will not actually change the world in any way. + * This method will use a sign at the location's block or a faked sign + * sent via {@link #sendBlockChange(Location, int, byte)} or + * {@link #sendBlockChange(Location, Material, byte)}. + *

    + * If the client does not have a sign at the given location it will + * display an error message to the user. + * + * @param loc the location of the sign + * @param lines the new text on the sign or null to clear it + * @throws IllegalArgumentException if location is null + * @throws IllegalArgumentException if lines is non-null and has a length less than 4 + */ + public void sendSignChange(Location loc, String[] lines) throws IllegalArgumentException; + + /** + * Render a map and send it to the player in its entirety. This may be + * used when streaming the map in the normal manner is not desirable. + * + * @param map The map to be sent + */ + public void sendMap(MapView map); + + /** + * Forces an update of the player's entire inventory. + * + */ + //@Deprecated // Spigot - undeprecate + public void updateInventory(); + + /** + * Awards the given achievement and any parent achievements that the + * player does not have. + * + * @param achievement Achievement to award + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public void awardAchievement(Achievement achievement); + + /** + * Removes the given achievement and any children achievements that the + * player has. + * + * @param achievement Achievement to remove + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public void removeAchievement(Achievement achievement); + + /** + * Gets whether this player has the given achievement. + * + * @param achievement the achievement to check + * @return whether the player has the achievement + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public boolean hasAchievement(Achievement achievement); + + /** + * Increments the given statistic for this player. + *

    + * This is equivalent to the following code: + * incrementStatistic(Statistic, 1) + * + * @param statistic Statistic to increment + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void incrementStatistic(Statistic statistic) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player. + *

    + * This is equivalent to the following code: + * decrementStatistic(Statistic, 1) + * + * @param statistic Statistic to decrement + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void decrementStatistic(Statistic statistic) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player. + * + * @param statistic Statistic to increment + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void incrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player. + * + * @param statistic Statistic to decrement + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException; + + /** + * Sets the given statistic for this player. + * + * @param statistic Statistic to set + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public int getStatistic(Statistic statistic) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given material. + *

    + * This is equivalent to the following code: + * incrementStatistic(Statistic, Material, 1) + * + * @param statistic Statistic to increment + * @param material Material to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given material. + *

    + * This is equivalent to the following code: + * decrementStatistic(Statistic, Material, 1) + * + * @param statistic Statistic to decrement + * @param material Material to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @param material Material offset of the statistic + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given material. + * + * @param statistic Statistic to increment + * @param material Material to offset the statistic with + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given material. + * + * @param statistic Statistic to decrement + * @param material Material to offset the statistic with + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException; + + /** + * Sets the given statistic for this player for the given material. + * + * @param statistic Statistic to set + * @param material Material to offset the statistic with + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given entity. + *

    + * This is equivalent to the following code: + * incrementStatistic(Statistic, EntityType, 1) + * + * @param statistic Statistic to increment + * @param entityType EntityType to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given entity. + *

    + * This is equivalent to the following code: + * decrementStatistic(Statistic, EntityType, 1) + * + * @param statistic Statistic to decrement + * @param entityType EntityType to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @param entityType EntityType offset of the statistic + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given entity. + * + * @param statistic Statistic to increment + * @param entityType EntityType to offset the statistic with + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given entity. + * + * @param statistic Statistic to decrement + * @param entityType EntityType to offset the statistic with + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(Statistic statistic, EntityType entityType, int amount); + + /** + * Sets the given statistic for this player for the given entity. + * + * @param statistic Statistic to set + * @param entityType EntityType to offset the statistic with + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void setStatistic(Statistic statistic, EntityType entityType, int newValue); + + /** + * Sets the current time on the player's client. When relative is true the + * player's time will be kept synchronized to its world time with the + * specified offset. + *

    + * When using non relative time the player's time will stay fixed at the + * specified time parameter. It's up to the caller to continue updating + * the player's time. To restore player time to normal use + * resetPlayerTime(). + * + * @param time The current player's perceived time or the player's time + * offset from the server time. + * @param relative When true the player time is kept relative to its world + * time. + */ + public void setPlayerTime(long time, boolean relative); + + /** + * Returns the player's current timestamp. + * + * @return The player's time + */ + public long getPlayerTime(); + + /** + * Returns the player's current time offset relative to server time, or + * the current player's fixed time if the player's time is absolute. + * + * @return The player's time + */ + public long getPlayerTimeOffset(); + + /** + * Returns true if the player's time is relative to the server time, + * otherwise the player's time is absolute and will not change its current + * time unless done so with setPlayerTime(). + * + * @return true if the player's time is relative to the server time. + */ + public boolean isPlayerTimeRelative(); + + /** + * Restores the normal condition where the player's time is synchronized + * with the server time. + *

    + * Equivalent to calling setPlayerTime(0, true). + */ + public void resetPlayerTime(); + + /** + * Sets the type of weather the player will see. When used, the weather + * status of the player is locked until {@link #resetPlayerWeather()} is + * used. + * + * @param type The WeatherType enum type the player should experience + */ + public void setPlayerWeather(WeatherType type); + + /** + * Returns the type of weather the player is currently experiencing. + * + * @return The WeatherType that the player is currently experiencing or + * null if player is seeing server weather. + */ + public WeatherType getPlayerWeather(); + + /** + * Restores the normal condition where the player's weather is controlled + * by server conditions. + */ + public void resetPlayerWeather(); + + /** + * Gives the player the amount of experience specified. + * + * @param amount Exp amount to give + */ + public void giveExp(int amount); + + /** + * Gives the player the amount of experience levels specified. Levels can + * be taken by specifying a negative amount. + * + * @param amount amount of experience levels to give or take + */ + public void giveExpLevels(int amount); + + /** + * Gets the players current experience points towards the next level. + *

    + * This is a percentage value. 0 is "no progress" and 1 is "next level". + * + * @return Current experience points + */ + public float getExp(); + + /** + * Sets the players current experience points towards the next level + *

    + * This is a percentage value. 0 is "no progress" and 1 is "next level". + * + * @param exp New experience points + */ + public void setExp(float exp); + + /** + * Gets the players current experience level + * + * @return Current experience level + */ + public int getLevel(); + + /** + * Sets the players current experience level + * + * @param level New experience level + */ + public void setLevel(int level); + + /** + * Gets the players total experience points. + *
    + * This refers to the total amount of experience the player has collected + * over time and is only displayed as the player's "score" upon dying. + * + * @return Current total experience points + */ + public int getTotalExperience(); + + /** + * Sets the players current experience points. + *
    + * This refers to the total amount of experience the player has collected + * over time and is only displayed as the player's "score" upon dying. + * + * @param exp New total experience points + */ + public void setTotalExperience(int exp); + + /** + * Gets the players current exhaustion level. + *

    + * Exhaustion controls how fast the food level drops. While you have a + * certain amount of exhaustion, your saturation will drop to zero, and + * then your food will drop to zero. + * + * @return Exhaustion level + */ + public float getExhaustion(); + + /** + * Sets the players current exhaustion level + * + * @param value Exhaustion level + */ + public void setExhaustion(float value); + + /** + * Gets the players current saturation level. + *

    + * Saturation is a buffer for food level. Your food level will not drop if + * you are saturated {@literal >} 0. + * + * @return Saturation level + */ + public float getSaturation(); + + /** + * Sets the players current saturation level + * + * @param value Saturation level + */ + public void setSaturation(float value); + + /** + * Gets the players current food level + * + * @return Food level + */ + public int getFoodLevel(); + + /** + * Sets the players current food level + * + * @param value New food level + */ + public void setFoodLevel(int value); + + /** + * Gets the Location where the player will spawn at their bed, null if + * they have not slept in one or their current bed spawn is invalid. + * + * @return Bed Spawn Location if bed exists, otherwise null. + */ + public Location getBedSpawnLocation(); + + /** + * Sets the Location where the player will spawn at their bed. + * + * @param location where to set the respawn location + */ + public void setBedSpawnLocation(Location location); + + /** + * Sets the Location where the player will spawn at their bed. + * + * @param location where to set the respawn location + * @param force whether to forcefully set the respawn location even if a + * valid bed is not present + */ + public void setBedSpawnLocation(Location location, boolean force); + + /** + * Determines if the Player is allowed to fly via jump key double-tap like + * in creative mode. + * + * @return True if the player is allowed to fly. + */ + public boolean getAllowFlight(); + + /** + * Sets if the Player is allowed to fly via jump key double-tap like in + * creative mode. + * + * @param flight If flight should be allowed. + */ + public void setAllowFlight(boolean flight); + + /** + * Hides a player from this player + * + * @param player Player to hide + * @deprecated see {@link #hidePlayer(Plugin, Player)} + */ + @Deprecated + public void hidePlayer(Player player); + + /** + * Hides a player from this player + * + * @param plugin Plugin that wants to hide the player + * @param player Player to hide + */ + public void hidePlayer(Plugin plugin, Player player); + + /** + * Allows this player to see a player that was previously hidden + * + * @param player Player to show + * @deprecated see {@link #showPlayer(Plugin, Player)} + */ + @Deprecated + public void showPlayer(Player player); + + /** + * Allows this player to see a player that was previously hidden. If + * another another plugin had hidden the player too, then the player will + * remain hidden until the other plugin calls this method too. + * + * @param plugin Plugin that wants to show the player + * @param player Player to show + */ + public void showPlayer(Plugin plugin, Player player); + + /** + * Checks to see if a player has been hidden from this player + * + * @param player Player to check + * @return True if the provided player is not being hidden from this + * player + */ + public boolean canSee(Player player); + + /** + * Checks to see if this player is currently flying or not. + * + * @return True if the player is flying, else false. + */ + public boolean isFlying(); + + /** + * Makes this player start or stop flying. + * + * @param value True to fly. + */ + public void setFlying(boolean value); + + /** + * Sets the speed at which a client will fly. Negative values indicate + * reverse directions. + * + * @param value The new speed, from -1 to 1. + * @throws IllegalArgumentException If new speed is less than -1 or + * greater than 1 + */ + public void setFlySpeed(float value) throws IllegalArgumentException; + + /** + * Sets the speed at which a client will walk. Negative values indicate + * reverse directions. + * + * @param value The new speed, from -1 to 1. + * @throws IllegalArgumentException If new speed is less than -1 or + * greater than 1 + */ + public void setWalkSpeed(float value) throws IllegalArgumentException; + + /** + * Gets the current allowed speed that a client can fly. + * + * @return The current allowed speed, from -1 to 1 + */ + public float getFlySpeed(); + + /** + * Gets the current allowed speed that a client can walk. + * + * @return The current allowed speed, from -1 to 1 + */ + public float getWalkSpeed(); + + /** + * Request that the player's client download and switch texture packs. + *

    + * The player's client will download the new texture pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached the same + * texture pack in the past, it will perform a file size check against + * the response content to determine if the texture pack has changed and + * needs to be downloaded again. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

    + * Notes: + *

      + *
    • Players can disable server textures on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
    • There is no concept of resetting texture packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
    • The request is send with "null" as the hash. This might result + * in newer versions not loading the pack correctly. + *
    + * + * @param url The URL from which the client will download the texture + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. + * @deprecated Minecraft no longer uses textures packs. Instead you + * should use {@link #setResourcePack(String)}. + */ + @Deprecated + public void setTexturePack(String url); + + /** + * Request that the player's client download and switch resource packs. + *

    + * The player's client will download the new resource pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached the same + * resource pack in the past, it will perform a file size check against + * the response content to determine if the resource pack has changed and + * needs to be downloaded again. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

    + * Notes: + *

      + *
    • Players can disable server resources on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
    • There is no concept of resetting resource packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
    • The request is send with "null" as the hash. This might result + * in newer versions not loading the pack correctly. + *
    + * + * @param url The URL from which the client will download the resource + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. The + * length restriction is an implementation specific arbitrary value. + */ + public void setResourcePack(String url); + + /** + * Request that the player's client download and switch resource packs. + *

    + * The player's client will download the new resource pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached a + * resource pack with the same hash in the past it will not download but + * directly apply the cached pack. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

    + * Notes: + *

      + *
    • Players can disable server resources on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
    • There is no concept of resetting resource packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
    + * + * @param url The URL from which the client will download the resource + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @param hash The sha1 hash sum of the resource pack file which is used + * to apply a cached version of the pack directly without downloading + * if it is available. Hast to be 20 bytes long! + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. The + * length restriction is an implementation specific arbitrary value. + * @throws IllegalArgumentException Thrown if the hash is null. + * @throws IllegalArgumentException Thrown if the hash is not 20 bytes + * long. + */ + public void setResourcePack(String url, byte[] hash); + + /** + * Gets the Scoreboard displayed to this player + * + * @return The current scoreboard seen by this player + */ + public Scoreboard getScoreboard(); + + /** + * Sets the player's visible Scoreboard. + * + * @param scoreboard New Scoreboard for the player + * @throws IllegalArgumentException if scoreboard is null + * @throws IllegalArgumentException if scoreboard was not created by the + * {@link org.bukkit.scoreboard.ScoreboardManager scoreboard manager} + * @throws IllegalStateException if this is a player that is not logged + * yet or has logged out + */ + public void setScoreboard(Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException; + + /** + * Gets if the client is displayed a 'scaled' health, that is, health on a + * scale from 0-{@link #getHealthScale()}. + * + * @return if client health display is scaled + * @see Player#setHealthScaled(boolean) + */ + public boolean isHealthScaled(); + + /** + * Sets if the client is displayed a 'scaled' health, that is, health on a + * scale from 0-{@link #getHealthScale()}. + *

    + * Displayed health follows a simple formula displayedHealth = + * getHealth() / getMaxHealth() * getHealthScale(). + * + * @param scale if the client health display is scaled + */ + public void setHealthScaled(boolean scale); + + /** + * Sets the number to scale health to for the client; this will also + * {@link #setHealthScaled(boolean) setHealthScaled(true)}. + *

    + * Displayed health follows a simple formula displayedHealth = + * getHealth() / getMaxHealth() * getHealthScale(). + * + * @param scale the number to scale health to + * @throws IllegalArgumentException if scale is <0 + * @throws IllegalArgumentException if scale is {@link Double#NaN} + * @throws IllegalArgumentException if scale is too high + */ + public void setHealthScale(double scale) throws IllegalArgumentException; + + /** + * Gets the number that health is scaled to for the client. + * + * @return the number that health would be scaled to for the client if + * HealthScaling is set to true + * @see Player#setHealthScale(double) + * @see Player#setHealthScaled(boolean) + */ + public double getHealthScale(); + + /** + * Gets the entity which is followed by the camera when in + * {@link GameMode#SPECTATOR}. + * + * @return the followed entity, or null if not in spectator mode or not + * following a specific entity. + */ + public Entity getSpectatorTarget(); + + /** + * Sets the entity which is followed by the camera when in + * {@link GameMode#SPECTATOR}. + * + * @param entity the entity to follow or null to reset + * @throws IllegalStateException if the player is not in + * {@link GameMode#SPECTATOR} + */ + public void setSpectatorTarget(Entity entity); + + /** + * Sends a title and a subtitle message to the player. If either of these + * values are null, they will not be sent and the display will remain + * unchanged. If they are empty strings, the display will be updated as + * such. If the strings contain a new line, only the first line will be + * sent. The titles will be displayed with the client's default timings. + * + * @param title Title text + * @param subtitle Subtitle text + * @deprecated API behavior subject to change + */ + @Deprecated + public void sendTitle(String title, String subtitle); + + /** + * Sends a title and a subtitle message to the player. If either of these + * values are null, they will not be sent and the display will remain + * unchanged. If they are empty strings, the display will be updated as + * such. If the strings contain a new line, only the first line will be + * sent. All timings values may take a value of -1 to indicate that they + * will use the last value sent (or the defaults if no title has been + * displayed). + * + * @param title Title text + * @param subtitle Subtitle text + * @param fadeIn time in ticks for titles to fade in. Defaults to 10. + * @param stay time in ticks for titles to stay. Defaults to 70. + * @param fadeOut time in ticks for titles to fade out. Defaults to 20. + */ + public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut); + + /** + * Resets the title displayed to the player. This will clear the displayed + * title / subtitle and reset timings to their default values. + */ + public void resetTitle(); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + */ + public void spawnParticle(Particle particle, Location location, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, T data); + + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + */ + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data); + + /** + * Return the player's progression on the specified advancement. + * + * @param advancement advancement + * @return object detailing the player's progress + */ + public AdvancementProgress getAdvancementProgress(Advancement advancement); + + /** + * Gets the player's current locale. + * + * The value of the locale String is not defined properly. + *
    + * The vanilla Minecraft client will use lowercase language / country pairs + * separated by an underscore, but custom resource packs may use any format + * they wish. + * + * @return the player's locale + */ + public String getLocale(); + + // Spigot start + public class Spigot extends Entity.Spigot + { + + /** + * Gets the connection address of this player, regardless of whether it + * has been spoofed or not. + * + * @return the player's connection address + */ + public InetSocketAddress getRawAddress() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Deprecated + public void playEffect(Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets whether the player collides with entities + * + * @return the player's collision toggle state + * @deprecated see {@link LivingEntity#isCollidable()} + */ + @Deprecated + public boolean getCollidesWithEntities() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Sets whether the player collides with entities + * + * @param collides whether the player should collide with entities or + * not. + * @deprecated {@link LivingEntity#setCollidable(boolean)} + */ + @Deprecated + public void setCollidesWithEntities(boolean collides) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Respawns the player if dead. + */ + public void respawn() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets player locale language. + * + * @return the player's client language settings + * @deprecated Use {@link Player#getLocale()} + */ + @Deprecated + public String getLocale() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets all players hidden with {@link #hidePlayer(Player)}. + * + * @return a Set with all hidden players + */ + public java.util.Set getHiddenPlayers() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Override + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendMessage(net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends the component to the specified screen position of this player + * + * @param position the screen position + * @param component the components to send + */ + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the specified screen position of this player + * + * @param position the screen position + * @param components the components to send + */ + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + @Override + Spigot spigot(); + // Spigot end + + // Paper start + /** + * Gets the view distance for this player + * @return the player's view distance + */ + int getViewDistance(); + + /** + * Sets the view distance for this player + * @param viewDistance the player's view distance + */ + void setViewDistance(int viewDistance); +} diff --git a/src/main/java/org/bukkit/entity/PolarBear.java b/src/main/java/org/bukkit/entity/PolarBear.java new file mode 100644 index 00000000..479f7a7c --- /dev/null +++ b/src/main/java/org/bukkit/entity/PolarBear.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a polar bear. + */ +public interface PolarBear extends Animals {} diff --git a/src/main/java/org/bukkit/entity/Projectile.java b/src/main/java/org/bukkit/entity/Projectile.java new file mode 100644 index 00000000..4ab2332f --- /dev/null +++ b/src/main/java/org/bukkit/entity/Projectile.java @@ -0,0 +1,40 @@ +package org.bukkit.entity; + +import org.bukkit.projectiles.ProjectileSource; + +/** + * Represents a shootable entity. + */ +public interface Projectile extends Entity { + + /** + * Retrieve the shooter of this projectile. + * + * @return the {@link ProjectileSource} that shot this projectile + */ + public ProjectileSource getShooter(); + + /** + * Set the shooter of this projectile. + * + * @param source the {@link ProjectileSource} that shot this projectile + */ + public void setShooter(ProjectileSource source); + + /** + * Determine if this projectile should bounce or not when it hits. + *

    + * If a small fireball does not bounce it will set the target on fire. + * + * @return true if it should bounce. + */ + public boolean doesBounce(); + + /** + * Set whether or not this projectile should bounce or not when it hits + * something. + * + * @param doesBounce whether or not it should bounce. + */ + public void setBounce(boolean doesBounce); +} diff --git a/src/main/java/org/bukkit/entity/Rabbit.java b/src/main/java/org/bukkit/entity/Rabbit.java new file mode 100644 index 00000000..1c8d1fcc --- /dev/null +++ b/src/main/java/org/bukkit/entity/Rabbit.java @@ -0,0 +1,49 @@ +package org.bukkit.entity; + +public interface Rabbit extends Animals { + + /** + * @return The type of rabbit. + */ + public Type getRabbitType(); + + /** + * @param type Sets the type of rabbit for this entity. + */ + public void setRabbitType(Type type); + + /** + * Represents the various types a Rabbit might be. + */ + public enum Type { + + /** + * Chocolate colored rabbit. + */ + BROWN, + /** + * Pure white rabbit. + */ + WHITE, + /** + * Black rabbit. + */ + BLACK, + /** + * Black with white patches, or white with black patches? + */ + BLACK_AND_WHITE, + /** + * Golden bunny. + */ + GOLD, + /** + * Salt and pepper colored, whatever that means. + */ + SALT_AND_PEPPER, + /** + * Rabbit with pure white fur, blood red horizontal eyes, and is hostile to players. + */ + THE_KILLER_BUNNY + } +} diff --git a/src/main/java/org/bukkit/entity/Sheep.java b/src/main/java/org/bukkit/entity/Sheep.java new file mode 100644 index 00000000..f4ce312c --- /dev/null +++ b/src/main/java/org/bukkit/entity/Sheep.java @@ -0,0 +1,19 @@ +package org.bukkit.entity; + +import org.bukkit.material.Colorable; + +/** + * Represents a Sheep. + */ +public interface Sheep extends Animals, Colorable { + + /** + * @return Whether the sheep is sheared. + */ + public boolean isSheared(); + + /** + * @param flag Whether to shear the sheep + */ + public void setSheared(boolean flag); +} diff --git a/src/main/java/org/bukkit/entity/Shulker.java b/src/main/java/org/bukkit/entity/Shulker.java new file mode 100644 index 00000000..3441bdb7 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Shulker.java @@ -0,0 +1,5 @@ +package org.bukkit.entity; + +import org.bukkit.material.Colorable; + +public interface Shulker extends Golem, Colorable {} diff --git a/src/main/java/org/bukkit/entity/ShulkerBullet.java b/src/main/java/org/bukkit/entity/ShulkerBullet.java new file mode 100644 index 00000000..f285c0a8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ShulkerBullet.java @@ -0,0 +1,18 @@ +package org.bukkit.entity; + +public interface ShulkerBullet extends Projectile { + + /** + * Retrieve the target of this bullet. + * + * @return the targeted entity + */ + Entity getTarget(); + + /** + * Sets the target of this bullet + * + * @param target the entity to target + */ + void setTarget(Entity target); +} diff --git a/src/main/java/org/bukkit/entity/Silverfish.java b/src/main/java/org/bukkit/entity/Silverfish.java new file mode 100644 index 00000000..fe01007f --- /dev/null +++ b/src/main/java/org/bukkit/entity/Silverfish.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Silverfish. + */ +public interface Silverfish extends Monster {} diff --git a/src/main/java/org/bukkit/entity/Sittable.java b/src/main/java/org/bukkit/entity/Sittable.java new file mode 100644 index 00000000..ea6ee26f --- /dev/null +++ b/src/main/java/org/bukkit/entity/Sittable.java @@ -0,0 +1,23 @@ +package org.bukkit.entity; + +/** + * An animal that can sit still. + */ +public interface Sittable { + + /** + * Checks if this animal is sitting + * + * @return true if sitting + */ + boolean isSitting(); + + /** + * Sets if this animal is sitting. Will remove any path that the animal + * was following beforehand. + * + * @param sitting true if sitting + */ + void setSitting(boolean sitting); + +} diff --git a/src/main/java/org/bukkit/entity/Skeleton.java b/src/main/java/org/bukkit/entity/Skeleton.java new file mode 100644 index 00000000..2a02ab85 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Skeleton.java @@ -0,0 +1,42 @@ +package org.bukkit.entity; + +/** + * Represents a Skeleton. + */ +public interface Skeleton extends Monster { + + /** + * Gets the current type of this skeleton. + * + * @return Current type + * @deprecated should check what class instance this is + */ + @Deprecated + public SkeletonType getSkeletonType(); + + /** + * @deprecated Must spawn a new subtype variant + */ + @Deprecated + public void setSkeletonType(SkeletonType type); + + /* + * @deprecated classes are different types + */ + @Deprecated + public enum SkeletonType { + + /** + * Standard skeleton type. + */ + NORMAL, + /** + * Wither skeleton. Generally found in Nether fortresses. + */ + WITHER, + /** + * Stray skeleton. Generally found in ice biomes. Shoots tipped arrows. + */ + STRAY; + } +} diff --git a/src/main/java/org/bukkit/entity/SkeletonHorse.java b/src/main/java/org/bukkit/entity/SkeletonHorse.java new file mode 100644 index 00000000..b2c6b6a8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/SkeletonHorse.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a SkeletonHorse - variant of {@link AbstractHorse}. + */ +public interface SkeletonHorse extends AbstractHorse { } diff --git a/src/main/java/org/bukkit/entity/Slime.java b/src/main/java/org/bukkit/entity/Slime.java new file mode 100644 index 00000000..0d87d203 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Slime.java @@ -0,0 +1,32 @@ +package org.bukkit.entity; + +/** + * Represents a Slime. + */ +public interface Slime extends LivingEntity { + + /** + * @return The size of the slime + */ + public int getSize(); + + /** + * @param sz The new size of the slime. + */ + public void setSize(int sz); + + /** + * Set the {@link LivingEntity} target for this slime. Set to null to clear + * the target. + * + * @param target the entity to target + */ + public void setTarget(LivingEntity target); + + /** + * Get the {@link LivingEntity} this slime is currently targeting. + * + * @return the current target, or null if no target exists. + */ + public LivingEntity getTarget(); +} diff --git a/src/main/java/org/bukkit/entity/SmallFireball.java b/src/main/java/org/bukkit/entity/SmallFireball.java new file mode 100644 index 00000000..33f54d3e --- /dev/null +++ b/src/main/java/org/bukkit/entity/SmallFireball.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a small {@link Fireball} + */ +public interface SmallFireball extends Fireball { + +} diff --git a/src/main/java/org/bukkit/entity/Snowball.java b/src/main/java/org/bukkit/entity/Snowball.java new file mode 100644 index 00000000..8c6b4333 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Snowball.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a snowball. + */ +public interface Snowball extends Projectile {} diff --git a/src/main/java/org/bukkit/entity/Snowman.java b/src/main/java/org/bukkit/entity/Snowman.java new file mode 100644 index 00000000..818efe2a --- /dev/null +++ b/src/main/java/org/bukkit/entity/Snowman.java @@ -0,0 +1,24 @@ +package org.bukkit.entity; + +/** + * Represents a snowman entity + */ +public interface Snowman extends Golem { + + /** + * Gets whether this snowman is in "derp mode", meaning it is not wearing a + * pumpkin. + * + * @return True if the snowman is bald, false if it is wearing a pumpkin + */ + boolean isDerp(); + + /** + * Sets whether this snowman is in "derp mode", meaning it is not wearing a + * pumpkin. NOTE: This value is not persisted to disk and will therefore + * reset when the chunk is reloaded. + * + * @param derpMode True to remove the pumpkin, false to add a pumpkin + */ + void setDerp(boolean derpMode); +} diff --git a/src/main/java/org/bukkit/entity/SpectralArrow.java b/src/main/java/org/bukkit/entity/SpectralArrow.java new file mode 100644 index 00000000..1a32341c --- /dev/null +++ b/src/main/java/org/bukkit/entity/SpectralArrow.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * Represents a spectral arrow. + */ +public interface SpectralArrow extends Arrow { + + /** + * Returns the amount of time that this arrow will apply + * the glowing effect for. + * + * @return the glowing effect ticks + */ + int getGlowingTicks(); + + /** + * Sets the amount of time to apply the glowing effect for. + * + * @param duration the glowing effect ticks + */ + void setGlowingTicks(int duration); +} diff --git a/src/main/java/org/bukkit/entity/Spellcaster.java b/src/main/java/org/bukkit/entity/Spellcaster.java new file mode 100644 index 00000000..31507225 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Spellcaster.java @@ -0,0 +1,52 @@ +package org.bukkit.entity; + +/** + * Represents a spell casting "Illager". + */ +public interface Spellcaster extends Illager { + + /** + * Represents the current spell the entity is using. + */ + public enum Spell { + + /** + * No spell is being used.. + */ + NONE, + /** + * The spell that summons Vexes. + */ + SUMMON_VEX, + /** + * The spell that summons Fangs. + */ + FANGS, + /** + * The "wololo" spell. + */ + WOLOLO, + /** + * The spell that makes the casting entity invisible. + */ + DISAPPEAR, + /** + * The spell that makes the target blind. + */ + BLINDNESS; + } + + /** + * Gets the {@link Spell} the entity is currently using. + * + * @return the current spell + */ + Spell getSpell(); + + /** + * Sets the {@link Spell} the entity is currently using. + * + * @param spell the spell the entity should be using + */ + void setSpell(Spell spell); +} diff --git a/src/main/java/org/bukkit/entity/Spider.java b/src/main/java/org/bukkit/entity/Spider.java new file mode 100644 index 00000000..f9ee8cc7 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Spider.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Spider. + */ +public interface Spider extends Monster {} diff --git a/src/main/java/org/bukkit/entity/SplashPotion.java b/src/main/java/org/bukkit/entity/SplashPotion.java new file mode 100644 index 00000000..2a210254 --- /dev/null +++ b/src/main/java/org/bukkit/entity/SplashPotion.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown splash potion bottle + */ +public interface SplashPotion extends ThrownPotion { } diff --git a/src/main/java/org/bukkit/entity/Squid.java b/src/main/java/org/bukkit/entity/Squid.java new file mode 100644 index 00000000..fb47968e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Squid.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Squid. + */ +public interface Squid extends WaterMob {} diff --git a/src/main/java/org/bukkit/entity/Stray.java b/src/main/java/org/bukkit/entity/Stray.java new file mode 100644 index 00000000..9c83f98e --- /dev/null +++ b/src/main/java/org/bukkit/entity/Stray.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Stray - variant of {@link Skeleton}. + */ +public interface Stray extends Skeleton { } diff --git a/src/main/java/org/bukkit/entity/TNTPrimed.java b/src/main/java/org/bukkit/entity/TNTPrimed.java new file mode 100644 index 00000000..fcd0a9b2 --- /dev/null +++ b/src/main/java/org/bukkit/entity/TNTPrimed.java @@ -0,0 +1,38 @@ +package org.bukkit.entity; + +/** + * Represents a Primed TNT. + */ +public interface TNTPrimed extends Explosive { + + /** + * Set the number of ticks until the TNT blows up after being primed. + * + * @param fuseTicks The fuse ticks + */ + public void setFuseTicks(int fuseTicks); + + /** + * Retrieve the number of ticks until the explosion of this TNTPrimed + * entity + * + * @return the number of ticks until this TNTPrimed explodes + */ + public int getFuseTicks(); + + /** + * Gets the source of this primed TNT. The source is the entity + * responsible for the creation of this primed TNT. (I.E. player ignites + * TNT with flint and steel.) Take note that this can be null if there is + * no suitable source. (created by the {@link + * org.bukkit.World#spawn(Location, Class)} method, for example.) + *

    + * The source will become null if the chunk this primed TNT is in is + * unloaded then reloaded. The source entity may be invalid if for example + * it has since died or been unloaded. Callers should check + * {@link Entity#isValid()}. + * + * @return the source of this primed TNT + */ + public Entity getSource(); +} diff --git a/src/main/java/org/bukkit/entity/Tameable.java b/src/main/java/org/bukkit/entity/Tameable.java new file mode 100644 index 00000000..98a936b2 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Tameable.java @@ -0,0 +1,53 @@ +package org.bukkit.entity; + +public interface Tameable extends Entity { + + /** + * Check if this is tamed + *

    + * If something is tamed then a player can not tame it through normal + * methods, even if it does not belong to anyone in particular. + * + * @return true if this has been tamed + */ + public boolean isTamed(); + + /** + * Sets if this has been tamed. Not necessary if the method setOwner has + * been used, as it tames automatically. + *

    + * If something is tamed then a player can not tame it through normal + * methods, even if it does not belong to anyone in particular. + * + * @param tame true if tame + */ + void setTamed(boolean tame); + + // Paper start + /** + * Gets the owners UUID + * + * @return the owners UUID, or null if not owned + */ + java.util.UUID getOwnerUUID(); + // Paper end + + /** + * Gets the current owning AnimalTamer + * + * @return the owning AnimalTamer, or null if not owned + */ + public AnimalTamer getOwner(); + + /** + * Set this to be owned by given AnimalTamer. + *

    + * If the owner is not null, this will be tamed and will have any current + * path it is following removed. If the owner is set to null, this will be + * untamed, and the current owner removed. + * + * @param tamer the AnimalTamer who should own this + */ + public void setOwner(AnimalTamer tamer); + +} diff --git a/src/main/java/org/bukkit/entity/ThrownExpBottle.java b/src/main/java/org/bukkit/entity/ThrownExpBottle.java new file mode 100644 index 00000000..671282ed --- /dev/null +++ b/src/main/java/org/bukkit/entity/ThrownExpBottle.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a thrown Experience bottle. + */ +public interface ThrownExpBottle extends Projectile { + +} diff --git a/src/main/java/org/bukkit/entity/ThrownPotion.java b/src/main/java/org/bukkit/entity/ThrownPotion.java new file mode 100644 index 00000000..81dcecb8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ThrownPotion.java @@ -0,0 +1,41 @@ +package org.bukkit.entity; + +import java.util.Collection; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +/** + * Represents a thrown potion bottle + */ +public interface ThrownPotion extends Projectile { + + /** + * Returns the effects that are applied by this potion. + * + * @return The potion effects + */ + public Collection getEffects(); + + /** + * Returns a copy of the ItemStack for this thrown potion. + *

    + * Altering this copy will not alter the thrown potion directly. If you want + * to alter the thrown potion, you must use the {@link + * #setItem(ItemStack) setItemStack} method. + * + * @return A copy of the ItemStack for this thrown potion. + */ + public ItemStack getItem(); + + /** + * Set the ItemStack for this thrown potion. + *

    + * The ItemStack must be of type {@link org.bukkit.Material#SPLASH_POTION} + * or {@link org.bukkit.Material#LINGERING_POTION}, otherwise an exception + * is thrown. + * + * @param item New ItemStack + */ + public void setItem(ItemStack item); +} diff --git a/src/main/java/org/bukkit/entity/TippedArrow.java b/src/main/java/org/bukkit/entity/TippedArrow.java new file mode 100644 index 00000000..09b5dd43 --- /dev/null +++ b/src/main/java/org/bukkit/entity/TippedArrow.java @@ -0,0 +1,93 @@ +package org.bukkit.entity; + +import java.util.List; + +import org.bukkit.Color; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public interface TippedArrow extends Arrow { + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + PotionData getBasePotionData(); + + /** + * Gets the color of this arrow. + * + * @return arrow color + */ + Color getColor(); + + /** + * Sets the color of this arrow. Will be applied as a tint to its particles. + * + * @param color arrow color + */ + void setColor(Color color); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this arrow. + *

    + * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + List getCustomEffects(); + + /** + * Adds a custom potion effect to this arrow. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the effect was added as a result of this call + */ + boolean addCustomEffect(PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this arrow. + * + * @param type the potion effect type to remove + * @return true if the an effect was removed as a result of this call + * @throws IllegalArgumentException if this operation would leave the Arrow + * in a state with no Custom Effects and PotionType.UNCRAFTABLE + */ + boolean removeCustomEffect(PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this arrow. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(PotionEffectType type); + + /** + * Removes all custom potion effects from this arrow. + * + * @throws IllegalArgumentException if this operation would leave the Arrow + * in a state with no Custom Effects and PotionType.UNCRAFTABLE + */ + void clearCustomEffects(); +} diff --git a/src/main/java/org/bukkit/entity/Vehicle.java b/src/main/java/org/bukkit/entity/Vehicle.java new file mode 100644 index 00000000..7d7607c4 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Vehicle.java @@ -0,0 +1,23 @@ +package org.bukkit.entity; + +import org.bukkit.util.Vector; + +/** + * Represents a vehicle entity. + */ +public interface Vehicle extends Entity { + + /** + * Gets the vehicle's velocity. + * + * @return velocity vector + */ + public Vector getVelocity(); + + /** + * Sets the vehicle's velocity. + * + * @param vel velocity vector + */ + public void setVelocity(Vector vel); +} diff --git a/src/main/java/org/bukkit/entity/Vex.java b/src/main/java/org/bukkit/entity/Vex.java new file mode 100644 index 00000000..a2f2fcae --- /dev/null +++ b/src/main/java/org/bukkit/entity/Vex.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Vex. + */ +public interface Vex extends Monster { } diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java new file mode 100644 index 00000000..f2095f61 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Villager.java @@ -0,0 +1,264 @@ +package org.bukkit.entity; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import java.util.List; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; + +/** + * Represents a villager NPC + */ +public interface Villager extends Ageable, NPC, InventoryHolder, Merchant { + + /** + * Gets the current profession of this villager. + * + * @return Current profession. + */ + public Profession getProfession(); + + /** + * Sets the new profession of this villager. + * + * @param profession New profession. + */ + public void setProfession(Profession profession); + + /** + * Get the current {@link Career} for this Villager. + * + * @return the {@link Career} + */ + public Career getCareer(); + + /** + * Set the new {@link Career} for this Villager. + * This method will reset the villager's trades to the new career. + * + * @param career the new career, or null to clear the career to a random one + * @throws IllegalArgumentException when the new {@link Career} cannot be + * used with this Villager's current {@link Profession}. + */ + public void setCareer(Career career); + + /** + * Set the new {@link Career} for this Villager. + * + * @param career the new career, or null to clear the career to a random one + * @param resetTrades true to reset this Villager's trades to the new + * career's (if any) + * @throws IllegalArgumentException when the new {@link Career} cannot be + * used with this Villager's current {@link Profession}. + */ + public void setCareer(Career career, boolean resetTrades); + + /** + * Gets this villager's inventory. + *
    + * Note that this inventory is not the Merchant inventory, rather, it is the + * items that a villager might have collected (from harvesting crops, etc.) + * + * {@inheritDoc} + */ + @Override + Inventory getInventory(); + + /** + * Gets this villager's riches, the number of emeralds this villager has + * been given. + * + * @return the villager's riches + */ + int getRiches(); + + /** + * Sets this villager's riches. + * + * @see Villager#getRiches() + * + * @param riches the new riches + */ + void setRiches(int riches); + + /** + * Represents the various different Villager professions there may be. + * Villagers have different trading options depending on their profession, + */ + public enum Profession { + /** + * Normal. Reserved for Zombies. + * @deprecated Unused + */ + @Deprecated + NORMAL(true), + /** + * Farmer profession. Wears a brown robe. + */ + FARMER(false), + /** + * Librarian profession. Wears a white robe. + */ + LIBRARIAN(false), + /** + * Priest profession. Wears a purple robe. + */ + PRIEST(false), + /** + * Blacksmith profession. Wears a black apron. + */ + BLACKSMITH(false), + /** + * Butcher profession. Wears a white apron. + */ + BUTCHER(false), + /** + * Nitwit profession. Wears a green apron, cannot trade. + */ + NITWIT(false), + /** + * Husk. Reserved for Zombies + * @deprecated Unused + */ + @Deprecated + HUSK(true); + private final boolean zombie; + + private Profession(boolean zombie) { + this.zombie = zombie; + } + + /** + * Returns if this profession can only be used by zombies. + * + * @return zombie profession status + * @deprecated Unused + */ + @Deprecated + public boolean isZombie() { + return zombie; + } + + /** + * Get an immutable list of {@link Career} belonging to this Profession. + * + * @return an immutable list of careers for this profession, or an empty + * map if this Profession has no careers. + */ + public List getCareers() { + return Career.getCareers(this); + } + } + + /** + * The Career of this Villager. + * Each {@link Profession} has a set of careers it is applicable to. Each + * career dictates the trading options that are generated. + */ + public enum Career { + /* + NOTE: The Career entries are order-specific. They should be maintained in the numerical order they are used in the CB implementation. + (e.g. Farmer careers are 1,2,3,4 so Career should reflect that numerical order in their ordinal status) + */ + // Farmer careers + /** + * Farmers primarily trade for food-related items. + */ + FARMER(Profession.FARMER), + /** + * Fisherman primarily trade for fish, as well as possibly selling + * string and/or coal. + */ + FISHERMAN(Profession.FARMER), + /** + * Shepherds primarily trade for wool items, and shears. + */ + SHEPHERD(Profession.FARMER), + /** + * Fletchers primarily trade for string, bows, and arrows. + */ + FLETCHER(Profession.FARMER), + // Librarian careers + /** + * Librarians primarily trade for paper, books, and enchanted books. + */ + LIBRARIAN(Profession.LIBRARIAN), + /** + * Cartographers primarily trade for explorer maps and some paper. + */ + CARTOGRAPHER(Profession.LIBRARIAN), + // Priest careers + /** + * Clerics primarily trade for rotten flesh, gold ingot, redstone, + * lapis, ender pearl, glowstone, and bottle o' enchanting. + */ + CLERIC(Profession.PRIEST), + // Blacksmith careers + /** + * Armorers primarily trade for iron armor, chainmail armor, and + * sometimes diamond armor. + */ + ARMORER(Profession.BLACKSMITH), + /** + * Weapon smiths primarily trade for iron and diamond weapons, sometimes + * enchanted. + */ + WEAPON_SMITH(Profession.BLACKSMITH), + /** + * Tool smiths primarily trade for iron and diamond tools. + */ + TOOL_SMITH(Profession.BLACKSMITH), + // Butcher careers + /** + * Butchers primarily trade for raw and cooked food. + */ + BUTCHER(Profession.BUTCHER), + /** + * Leatherworkers primarily trade for leather, and leather armor, as + * well as saddles. + */ + LEATHERWORKER(Profession.BUTCHER), + // Nitwit + /** + * Nitwit villagers do not do anything. They do not have any trades by + * default. + */ + NITWIT(Profession.NITWIT); + + private static final Multimap careerMap = LinkedListMultimap.create(); + private final Profession profession; + + private Career(Profession profession) { + this.profession = profession; + } + + /** + * Get the {@link Profession} this {@link Career} belongs to. + * + * @return the {@link Profession}. + */ + public Profession getProfession() { + return profession; + } + + /** + * Get an immutable list of {@link Career}s that can be used with a + * given {@link Profession} + * + * @param profession the profession to get careers for + * @return an immutable list of Careers that can be used by a + * profession, or an empty map if the profession was not found + */ + public static List getCareers(Profession profession) { + return careerMap.containsKey(profession) ? ImmutableList.copyOf(careerMap.get(profession)) : ImmutableList.of(); + } + + static { + for (Career career : Career.values()) { + careerMap.put(career.profession, career); + } + } + } +} diff --git a/src/main/java/org/bukkit/entity/Vindicator.java b/src/main/java/org/bukkit/entity/Vindicator.java new file mode 100644 index 00000000..b8ea68a8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Vindicator.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Vindicator. + */ +public interface Vindicator extends Illager { } diff --git a/src/main/java/org/bukkit/entity/WaterMob.java b/src/main/java/org/bukkit/entity/WaterMob.java new file mode 100644 index 00000000..3e89ca0c --- /dev/null +++ b/src/main/java/org/bukkit/entity/WaterMob.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Water Mob + */ +public interface WaterMob extends LivingEntity {} diff --git a/src/main/java/org/bukkit/entity/Weather.java b/src/main/java/org/bukkit/entity/Weather.java new file mode 100644 index 00000000..6d77851f --- /dev/null +++ b/src/main/java/org/bukkit/entity/Weather.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Weather related entity, such as a storm + */ +public interface Weather extends Entity {} diff --git a/src/main/java/org/bukkit/entity/Witch.java b/src/main/java/org/bukkit/entity/Witch.java new file mode 100644 index 00000000..9c5dc1f9 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Witch.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a Witch + */ +public interface Witch extends Monster { +} diff --git a/src/main/java/org/bukkit/entity/Wither.java b/src/main/java/org/bukkit/entity/Wither.java new file mode 100644 index 00000000..0922c5c6 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Wither.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a Wither boss + */ +public interface Wither extends Monster { +} diff --git a/src/main/java/org/bukkit/entity/WitherSkeleton.java b/src/main/java/org/bukkit/entity/WitherSkeleton.java new file mode 100644 index 00000000..7045014e --- /dev/null +++ b/src/main/java/org/bukkit/entity/WitherSkeleton.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a WitherSkeleton - variant of {@link Skeleton}. + */ +public interface WitherSkeleton extends Skeleton { } diff --git a/src/main/java/org/bukkit/entity/WitherSkull.java b/src/main/java/org/bukkit/entity/WitherSkull.java new file mode 100644 index 00000000..33d20abc --- /dev/null +++ b/src/main/java/org/bukkit/entity/WitherSkull.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents a wither skull {@link Fireball}. + */ +public interface WitherSkull extends Fireball { + + /** + * Sets the charged status of the wither skull. + * + * @param charged whether it should be charged + */ + public void setCharged(boolean charged); + + /** + * Gets whether or not the wither skull is charged. + * + * @return whether the wither skull is charged + */ + public boolean isCharged(); +} diff --git a/src/main/java/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java new file mode 100644 index 00000000..c0905c05 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Wolf.java @@ -0,0 +1,40 @@ +package org.bukkit.entity; + +import org.bukkit.DyeColor; + +/** + * Represents a Wolf + */ +public interface Wolf extends Animals, Tameable, Sittable { + + /** + * Checks if this wolf is angry + * + * @return Anger true if angry + */ + public boolean isAngry(); + + /** + * Sets the anger of this wolf. + *

    + * An angry wolf can not be fed or tamed, and will actively look for + * targets to attack. + * + * @param angry true if angry + */ + public void setAngry(boolean angry); + + /** + * Get the collar color of this wolf + * + * @return the color of the collar + */ + public DyeColor getCollarColor(); + + /** + * Set the collar color of this wolf + * + * @param color the color to apply + */ + public void setCollarColor(DyeColor color); +} diff --git a/src/main/java/org/bukkit/entity/Zombie.java b/src/main/java/org/bukkit/entity/Zombie.java new file mode 100644 index 00000000..cf53ea22 --- /dev/null +++ b/src/main/java/org/bukkit/entity/Zombie.java @@ -0,0 +1,51 @@ +package org.bukkit.entity; + +/** + * Represents a Zombie. + */ +public interface Zombie extends Monster { + + /** + * Gets whether the zombie is a baby + * + * @return Whether the zombie is a baby + */ + public boolean isBaby(); + + /** + * Sets whether the zombie is a baby + * + * @param flag Whether the zombie is a baby + */ + public void setBaby(boolean flag); + + /** + * Gets whether the zombie is a villager + * + * @return Whether the zombie is a villager + * @deprecated check if instanceof {@link ZombieVillager}. + */ + @Deprecated + public boolean isVillager(); + + /** + * @param flag + * @deprecated must spawn {@link ZombieVillager}. + */ + @Deprecated + public void setVillager(boolean flag); + + /** + * @param profession + * @see ZombieVillager#getVillagerProfession() + */ + @Deprecated + public void setVillagerProfession(Villager.Profession profession); + + /** + * @return profession + * @see ZombieVillager#getVillagerProfession() + */ + @Deprecated + public Villager.Profession getVillagerProfession(); +} diff --git a/src/main/java/org/bukkit/entity/ZombieHorse.java b/src/main/java/org/bukkit/entity/ZombieHorse.java new file mode 100644 index 00000000..4179b68b --- /dev/null +++ b/src/main/java/org/bukkit/entity/ZombieHorse.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a ZombieHorse - variant of {@link AbstractHorse}. + */ +public interface ZombieHorse extends AbstractHorse { } diff --git a/src/main/java/org/bukkit/entity/ZombieVillager.java b/src/main/java/org/bukkit/entity/ZombieVillager.java new file mode 100644 index 00000000..f6ab96c8 --- /dev/null +++ b/src/main/java/org/bukkit/entity/ZombieVillager.java @@ -0,0 +1,19 @@ +package org.bukkit.entity; + +/** + * Represents a {@link Zombie} which was once a {@link Villager}. + */ +public interface ZombieVillager extends Zombie { + + /** + * Sets the villager profession of this zombie. + */ + public void setVillagerProfession(Villager.Profession profession); + + /** + * Returns the villager profession of this zombie. + * + * @return the profession or null + */ + public Villager.Profession getVillagerProfession(); +} diff --git a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java new file mode 100644 index 00000000..e5026807 --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java @@ -0,0 +1,36 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Minecart; + +public interface CommandMinecart extends Minecart, CommandSender { + + /** + * Gets the command that this CommandMinecart will run when activated. + * This will never return null. If the CommandMinecart does not have a + * command, an empty String will be returned instead. + * + * @return Command that this CommandMinecart will run when powered. + */ + public String getCommand(); + + /** + * Sets the command that this CommandMinecart will run when activated. + * Setting the command to null is the same as setting it to an empty + * String. + * + * @param command Command that this CommandMinecart will run when + * activated. + */ + public void setCommand(String command); + + /** + * Sets the name of this CommandMinecart. The name is used with commands + * that this CommandMinecart executes. Setting the name to null is the + * same as setting it to "@". + * + * @param name New name for this CommandMinecart. + */ + public void setName(String name); + +} diff --git a/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java b/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java new file mode 100644 index 00000000..a4411dac --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java @@ -0,0 +1,9 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a Minecart with TNT inside it that can explode when triggered. + */ +public interface ExplosiveMinecart extends Minecart { +} diff --git a/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java b/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java new file mode 100644 index 00000000..03304319 --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java @@ -0,0 +1,25 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a Minecart with a Hopper inside it + */ +public interface HopperMinecart extends Minecart, InventoryHolder { + + /** + * Checks whether or not this Minecart will pick up + * items into its inventory. + * + * @return true if the Minecart will pick up items + */ + boolean isEnabled(); + + /** + * Sets whether this Minecart will pick up items. + * + * @param enabled new enabled state + */ + void setEnabled(boolean enabled); +} diff --git a/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java b/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java new file mode 100644 index 00000000..57e8b1d5 --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java @@ -0,0 +1,10 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a powered minecart. A powered minecart moves on its own when a + * player deposits {@link org.bukkit.Material#COAL fuel}. + */ +public interface PoweredMinecart extends Minecart { +} diff --git a/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java b/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java new file mode 100644 index 00000000..1b826457 --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java @@ -0,0 +1,14 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a minecart that can have certain {@link + * org.bukkit.entity.Entity entities} as passengers. Normal passengers + * include all {@link org.bukkit.entity.LivingEntity living entities} with + * the exception of {@link org.bukkit.entity.IronGolem iron golems}. + * Non-player entities that meet normal passenger criteria automatically + * mount these minecarts when close enough. + */ +public interface RideableMinecart extends Minecart { +} diff --git a/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java b/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java new file mode 100644 index 00000000..0ce3592e --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java @@ -0,0 +1,10 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a Minecart with an {@link org.bukkit.block.CreatureSpawner + * entity spawner} inside it. + */ +public interface SpawnerMinecart extends Minecart { +} diff --git a/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java b/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java new file mode 100644 index 00000000..4f04ab40 --- /dev/null +++ b/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java @@ -0,0 +1,12 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; +import org.bukkit.inventory.InventoryHolder; + +/** + * Represents a minecart with a chest. These types of {@link Minecart + * minecarts} have their own inventory that can be accessed using methods + * from the {@link InventoryHolder} interface. + */ +public interface StorageMinecart extends Minecart, InventoryHolder { +} diff --git a/src/main/java/org/bukkit/event/Cancellable.java b/src/main/java/org/bukkit/event/Cancellable.java new file mode 100644 index 00000000..799b0b0f --- /dev/null +++ b/src/main/java/org/bukkit/event/Cancellable.java @@ -0,0 +1,20 @@ +package org.bukkit.event; + +public interface Cancellable { + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + * + * @return true if this event is cancelled + */ + public boolean isCancelled(); + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel); +} diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java new file mode 100644 index 00000000..f5971535 --- /dev/null +++ b/src/main/java/org/bukkit/event/Event.java @@ -0,0 +1,115 @@ +package org.bukkit.event; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; + +/** + * Represents an event. + * + * All events require a static method named getHandlerList() which returns the same {@link HandlerList} as {@link #getHandlers()}. + * + * @see PluginManager#callEvent(Event) + * @see PluginManager#registerEvents(Listener,Plugin) + */ +public abstract class Event { + private String name; + private final boolean async; + + /** + * The default constructor is defined for cleaner code. This constructor + * assumes the event is synchronous. + */ + public Event() { + this(false); + } + + /** + * This constructor is used to explicitly declare an event as synchronous + * or asynchronous. + * + * @param isAsync true indicates the event will fire asynchronously, false + * by default from default constructor + */ + public Event(boolean isAsync) { + this.async = isAsync; + } + + // Paper start + /** + * Calls the event and tests if cancelled. + * + * @return false if event was cancelled, if cancellable. otherwise true. + */ + public boolean callEvent() { + Bukkit.getPluginManager().callEvent(this); + if (this instanceof Cancellable) { + return !((Cancellable) this).isCancelled(); + } else { + return true; + } + } + // Paper end + + /** + * Convenience method for providing a user-friendly identifier. By + * default, it is the event's class's {@linkplain Class#getSimpleName() + * simple name}. + * + * @return name of this event + */ + public String getEventName() { + if (name == null) { + name = getClass().getSimpleName(); + } + return name; + } + + public abstract HandlerList getHandlers(); + + /** + * Any custom event that should not by synchronized with other events must + * use the specific constructor. These are the caveats of using an + * asynchronous event: + *

      + *
    • The event is never fired from inside code triggered by a + * synchronous event. Attempting to do so results in an {@link + * IllegalStateException}. + *
    • However, asynchronous event handlers may fire synchronous or + * asynchronous events + *
    • The event may be fired multiple times simultaneously and in any + * order. + *
    • Any newly registered or unregistered handler is ignored after an + * event starts execution. + *
    • The handlers for this event may block for any length of time. + *
    • Some implementations may selectively declare a specific event use + * as asynchronous. This behavior should be clearly defined. + *
    • Asynchronous calls are not calculated in the plugin timing system. + *
    + * + * @return false by default, true if the event fires asynchronously + */ + public final boolean isAsynchronous() { + return async; + } + + public enum Result { + + /** + * Deny the event. Depending on the event, the action indicated by the + * event will either not take place or will be reverted. Some actions + * may not be denied. + */ + DENY, + /** + * Neither deny nor allow the event. The server will proceed with its + * normal handling. + */ + DEFAULT, + /** + * Allow / Force the event. The action indicated by the event will + * take place if possible, even if the server would not normally allow + * the action. Some actions may not be allowed. + */ + ALLOW; + } +} diff --git a/src/main/java/org/bukkit/event/EventException.java b/src/main/java/org/bukkit/event/EventException.java new file mode 100644 index 00000000..84638e85 --- /dev/null +++ b/src/main/java/org/bukkit/event/EventException.java @@ -0,0 +1,53 @@ +package org.bukkit.event; + +public class EventException extends Exception { + private static final long serialVersionUID = 3532808232324183999L; + private final Throwable cause; + + /** + * Constructs a new EventException based on the given Exception + * + * @param throwable Exception that triggered this Exception + */ + public EventException(Throwable throwable) { + cause = throwable; + } + + /** + * Constructs a new EventException + */ + public EventException() { + cause = null; + } + + /** + * Constructs a new EventException with the given message + * + * @param cause The exception that caused this + * @param message The message + */ + public EventException(Throwable cause, String message) { + super(message); + this.cause = cause; + } + + /** + * Constructs a new EventException with the given message + * + * @param message The message + */ + public EventException(String message) { + super(message); + cause = null; + } + + /** + * If applicable, returns the Exception that triggered this Exception + * + * @return Inner exception, or null if one does not exist + */ + @Override + public Throwable getCause() { + return cause; + } +} diff --git a/src/main/java/org/bukkit/event/EventHandler.java b/src/main/java/org/bukkit/event/EventHandler.java new file mode 100644 index 00000000..4c9fb3cd --- /dev/null +++ b/src/main/java/org/bukkit/event/EventHandler.java @@ -0,0 +1,41 @@ +package org.bukkit.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to mark methods as being event handler methods + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EventHandler { + + /** + * Define the priority of the event. + *

    + * First priority to the last priority executed: + *

      + *
    1. LOWEST + *
    2. LOW + *
    3. NORMAL + *
    4. HIGH + *
    5. HIGHEST + *
    6. MONITOR + *
    + * + * @return the priority + */ + EventPriority priority() default EventPriority.NORMAL; + + /** + * Define if the handler ignores a cancelled event. + *

    + * If ignoreCancelled is true and the event is cancelled, the method is + * not called. Otherwise, the method is always called. + * + * @return whether cancelled events should be ignored + */ + boolean ignoreCancelled() default false; +} diff --git a/src/main/java/org/bukkit/event/EventPriority.java b/src/main/java/org/bukkit/event/EventPriority.java new file mode 100644 index 00000000..61ffa50f --- /dev/null +++ b/src/main/java/org/bukkit/event/EventPriority.java @@ -0,0 +1,47 @@ +package org.bukkit.event; + +/** + * Represents an event's priority in execution + */ +public enum EventPriority { + + /** + * Event call is of very low importance and should be ran first, to allow + * other plugins to further customise the outcome + */ + LOWEST(0), + /** + * Event call is of low importance + */ + LOW(1), + /** + * Event call is neither important nor unimportant, and may be ran + * normally + */ + NORMAL(2), + /** + * Event call is of high importance + */ + HIGH(3), + /** + * Event call is critical and must have the final say in what happens + * to the event + */ + HIGHEST(4), + /** + * Event is listened to purely for monitoring the outcome of an event. + *

    + * No modifications to the event should be made under this priority + */ + MONITOR(5); + + private final int slot; + + private EventPriority(int slot) { + this.slot = slot; + } + + public int getSlot() { + return slot; + } +} diff --git a/src/main/java/org/bukkit/event/HandlerList.java b/src/main/java/org/bukkit/event/HandlerList.java new file mode 100644 index 00000000..7d5efffb --- /dev/null +++ b/src/main/java/org/bukkit/event/HandlerList.java @@ -0,0 +1,231 @@ +package org.bukkit.event; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; + +import java.util.*; +import java.util.Map.Entry; + +/** + * A list of event handlers, stored per-event. Based on lahwran's fevents. + */ +public class HandlerList { + + /** + * Handler array. This field being an array is the key to this system's + * speed. + */ + private volatile RegisteredListener[] handlers = null; + + /** + * Dynamic handler lists. These are changed using register() and + * unregister() and are automatically baked to the handlers array any time + * they have changed. + */ + private final EnumMap> handlerslots; + + /** + * List of all HandlerLists which have been created, for use in bakeAll() + */ + private static ArrayList allLists = new ArrayList(); + + /** + * Bake all handler lists. Best used just after all normal event + * registration is complete, ie just after all plugins are loaded if + * you're using fevents in a plugin system. + */ + public static void bakeAll() { + synchronized (allLists) { + for (HandlerList h : allLists) { + h.bake(); + } + } + } + + /** + * Unregister all listeners from all handler lists. + */ + public static void unregisterAll() { + synchronized (allLists) { + for (HandlerList h : allLists) { + synchronized (h) { + for (List list : h.handlerslots.values()) { + list.clear(); + } + h.handlers = null; + } + } + } + } + + /** + * Unregister a specific plugin's listeners from all handler lists. + * + * @param plugin plugin to unregister + */ + public static void unregisterAll(Plugin plugin) { + synchronized (allLists) { + for (HandlerList h : allLists) { + h.unregister(plugin); + } + } + } + + /** + * Unregister a specific listener from all handler lists. + * + * @param listener listener to unregister + */ + public static void unregisterAll(Listener listener) { + synchronized (allLists) { + for (HandlerList h : allLists) { + h.unregister(listener); + } + } + } + + /** + * Create a new handler list and initialize using EventPriority. + *

    + * The HandlerList is then added to meta-list for use in bakeAll() + */ + public HandlerList() { + handlerslots = new EnumMap>(EventPriority.class); + for (EventPriority o : EventPriority.values()) { + handlerslots.put(o, new ArrayList()); + } + synchronized (allLists) { + allLists.add(this); + } + } + + /** + * Register a new listener in this handler list + * + * @param listener listener to register + */ + public synchronized void register(RegisteredListener listener) { + if (handlerslots.get(listener.getPriority()).contains(listener)) + throw new IllegalStateException("This listener is already registered to priority " + listener.getPriority().toString()); + handlers = null; + handlerslots.get(listener.getPriority()).add(listener); + } + + /** + * Register a collection of new listeners in this handler list + * + * @param listeners listeners to register + */ + public void registerAll(Collection listeners) { + for (RegisteredListener listener : listeners) { + register(listener); + } + } + + /** + * Remove a listener from a specific order slot + * + * @param listener listener to remove + */ + public synchronized void unregister(RegisteredListener listener) { + if (handlerslots.get(listener.getPriority()).remove(listener)) { + handlers = null; + } + } + + /** + * Remove a specific plugin's listeners from this handler + * + * @param plugin plugin to remove + */ + public synchronized void unregister(Plugin plugin) { + boolean changed = false; + for (List list : handlerslots.values()) { + for (ListIterator i = list.listIterator(); i.hasNext();) { + if (i.next().getPlugin().equals(plugin)) { + i.remove(); + changed = true; + } + } + } + if (changed) handlers = null; + } + + /** + * Remove a specific listener from this handler + * + * @param listener listener to remove + */ + public synchronized void unregister(Listener listener) { + boolean changed = false; + for (List list : handlerslots.values()) { + for (ListIterator i = list.listIterator(); i.hasNext();) { + if (i.next().getListener().equals(listener)) { + i.remove(); + changed = true; + } + } + } + if (changed) handlers = null; + } + + /** + * Bake HashMap and ArrayLists to 2d array - does nothing if not necessary + */ + public synchronized void bake() { + if (handlers != null) return; // don't re-bake when still valid + List entries = new ArrayList(); + for (Entry> entry : handlerslots.entrySet()) { + entries.addAll(entry.getValue()); + } + handlers = entries.toArray(new RegisteredListener[entries.size()]); + } + + /** + * Get the baked registered listeners associated with this handler list + * + * @return the array of registered listeners + */ + public RegisteredListener[] getRegisteredListeners() { + RegisteredListener[] handlers; + while ((handlers = this.handlers) == null) bake(); // This prevents fringe cases of returning null + return handlers; + } + + /** + * Get a specific plugin's registered listeners associated with this + * handler list + * + * @param plugin the plugin to get the listeners of + * @return the list of registered listeners + */ + public static ArrayList getRegisteredListeners(Plugin plugin) { + ArrayList listeners = new ArrayList(); + synchronized (allLists) { + for (HandlerList h : allLists) { + synchronized (h) { + for (List list : h.handlerslots.values()) { + for (RegisteredListener listener : list) { + if (listener.getPlugin().equals(plugin)) { + listeners.add(listener); + } + } + } + } + } + } + return listeners; + } + + /** + * Get a list of all handler lists for every event type + * + * @return the list of all handler lists + */ + @SuppressWarnings("unchecked") + public static ArrayList getHandlerLists() { + synchronized (allLists) { + return (ArrayList) allLists.clone(); + } + } +} diff --git a/src/main/java/org/bukkit/event/Listener.java b/src/main/java/org/bukkit/event/Listener.java new file mode 100644 index 00000000..ff083e62 --- /dev/null +++ b/src/main/java/org/bukkit/event/Listener.java @@ -0,0 +1,6 @@ +package org.bukkit.event; + +/** + * Simple interface for tagging all EventListeners + */ +public interface Listener {} diff --git a/src/main/java/org/bukkit/event/block/Action.java b/src/main/java/org/bukkit/event/block/Action.java new file mode 100644 index 00000000..25d26e3f --- /dev/null +++ b/src/main/java/org/bukkit/event/block/Action.java @@ -0,0 +1,33 @@ +package org.bukkit.event.block; + +public enum Action { + + /** + * Left-clicking a block + */ + LEFT_CLICK_BLOCK, + /** + * Right-clicking a block + */ + RIGHT_CLICK_BLOCK, + /** + * Left-clicking the air + */ + LEFT_CLICK_AIR, + /** + * Right-clicking the air + */ + RIGHT_CLICK_AIR, + /** + * Stepping onto or into a block (Ass-pressure) + * + * Examples: + *

      + *
    • Jumping on soil + *
    • Standing on pressure plate + *
    • Triggering redstone ore + *
    • Triggering tripwire + *
    + */ + PHYSICAL, +} diff --git a/src/main/java/org/bukkit/event/block/BlockBreakEvent.java b/src/main/java/org/bukkit/event/block/BlockBreakEvent.java new file mode 100644 index 00000000..d1b5f7c5 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockBreakEvent.java @@ -0,0 +1,74 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; + +/** + * Called when a block is broken by a player. + *

    + * If you wish to have the block drop experience, you must set the experience + * value above 0. By default, experience will be set in the event if: + *

      + *
    1. The player is not in creative or adventure mode + *
    2. The player can loot the block (ie: does not destroy it completely, by + * using the correct tool) + *
    3. The player does not have silk touch + *
    4. The block drops experience in vanilla Minecraft + *
    + *

    + * Note: + * Plugins wanting to simulate a traditional block drop should set the block + * to air and utilize their own methods for determining what the default drop + * for the block being broken is and what to do about it, if anything. + *

    + * If a Block Break event is cancelled, the block will not break and + * experience will not drop. + */ +public class BlockBreakEvent extends BlockExpEvent implements Cancellable { + private final Player player; + private boolean dropItems; + private boolean cancel; + + public BlockBreakEvent(final Block theBlock, final Player player) { + super(theBlock, 0); + + this.player = player; + this.dropItems = true; // Defaults to dropping items as it normally would + } + + /** + * Gets the Player that is breaking the block involved in this event. + * + * @return The Player that is breaking the block involved in this event + */ + public Player getPlayer() { + return player; + } + + /** + * Sets whether or not the block will drop items as it normally would. + * + * @param dropItems Whether or not the block will drop items + */ + public void setDropItems(boolean dropItems) { + this.dropItems = dropItems; + } + + /** + * Gets whether or not the block will drop items. + * + * @return Whether or not the block will drop items + */ + public boolean isDropItems() { + return this.dropItems; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockBurnEvent.java b/src/main/java/org/bukkit/event/block/BlockBurnEvent.java new file mode 100644 index 00000000..e7934d2a --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockBurnEvent.java @@ -0,0 +1,54 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a block is destroyed as a result of being burnt by fire. + *

    + * If a Block Burn event is cancelled, the block will not be destroyed as a + * result of being burnt by fire. + */ +public class BlockBurnEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Block ignitingBlock; + + @Deprecated + public BlockBurnEvent(final Block block) { + this(block, null); + } + + public BlockBurnEvent(final Block block, final Block ignitingBlock) { + super(block); + this.ignitingBlock = ignitingBlock; + } + + /** + * Gets the block which ignited this block. + * + * @return The Block that ignited and burned this block, or null if no + * source block exists + */ + public Block getIgnitingBlock() { + return ignitingBlock; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java b/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java new file mode 100644 index 00000000..613feb91 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java @@ -0,0 +1,93 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.Material; +import org.bukkit.event.HandlerList; + +/** + * Called when we try to place a block, to see if we can build it here or not. + *

    + * Note: + *

      + *
    • The Block returned by getBlock() is the block we are trying to place + * on, not the block we are trying to place. + *
    • If you want to figure out what is being placed, use {@link + * #getMaterial()} or {@link #getMaterialId()} instead. + *
    + */ +public class BlockCanBuildEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean buildable; + + /** + * + * @deprecated Magic value + */ + @Deprecated + protected int material; + + /** + * + * @deprecated Magic value + * @param block the block involved in this event + * @param id the id of the block to place + * @param canBuild whether we can build + */ + @Deprecated + public BlockCanBuildEvent(final Block block, final int id, final boolean canBuild) { + super(block); + buildable = canBuild; + material = id; + } + + /** + * Gets whether or not the block can be built here. + *

    + * By default, returns Minecraft's answer on whether the block can be + * built here or not. + * + * @return boolean whether or not the block can be built + */ + public boolean isBuildable() { + return buildable; + } + + /** + * Sets whether the block can be built here or not. + * + * @param cancel true if you want to allow the block to be built here + * despite Minecraft's default behaviour + */ + public void setBuildable(boolean cancel) { + this.buildable = cancel; + } + + /** + * Gets the Material that we are trying to place. + * + * @return The Material that we are trying to place + */ + public Material getMaterial() { + return Material.getMaterial(material); + } + + /** + * Gets the Material ID for the Material that we are trying to place. + * + * @return The Material ID for the Material that we are trying to place + * @deprecated Magic value + */ + @Deprecated + public int getMaterialId() { + return material; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockDamageEvent.java b/src/main/java/org/bukkit/event/block/BlockDamageEvent.java new file mode 100644 index 00000000..d80e00ec --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockDamageEvent.java @@ -0,0 +1,83 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a block is damaged by a player. + *

    + * If a Block Damage event is cancelled, the block will not be damaged. + */ +public class BlockDamageEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Player player; + private boolean instaBreak; + private boolean cancel; + private final ItemStack itemstack; + + public BlockDamageEvent(final Player player, final Block block, final ItemStack itemInHand, final boolean instaBreak) { + super(block); + this.instaBreak = instaBreak; + this.player = player; + this.itemstack = itemInHand; + this.cancel = false; + } + + /** + * Gets the player damaging the block involved in this event. + * + * @return The player damaging the block involved in this event + */ + public Player getPlayer() { + return player; + } + + /** + * Gets if the block is set to instantly break when damaged by the player. + * + * @return true if the block should instantly break when damaged by the + * player + */ + public boolean getInstaBreak() { + return instaBreak; + } + + /** + * Sets if the block should instantly break when damaged by the player. + * + * @param bool true if you want the block to instantly break when damaged + * by the player + */ + public void setInstaBreak(boolean bool) { + this.instaBreak = bool; + } + + /** + * Gets the ItemStack for the item currently in the player's hand. + * + * @return The ItemStack for the item currently in the player's hand + */ + public ItemStack getItemInHand() { + return itemstack; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java b/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java new file mode 100644 index 00000000..253c09dc --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +/** + * Called when an item is dispensed from a block. + *

    + * If a Block Dispense event is cancelled, the block will not dispense the + * item. + */ +public class BlockDispenseEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + private ItemStack item; + private Vector velocity; + + public BlockDispenseEvent(final Block block, final ItemStack dispensed, final Vector velocity) { + super(block); + this.item = dispensed; + this.velocity = velocity; + } + + /** + * Gets the item that is being dispensed. Modifying the returned item will + * have no effect, you must use {@link + * #setItem(ItemStack)} instead. + * + * @return An ItemStack for the item being dispensed + */ + public ItemStack getItem() { + return item.clone(); + } + + /** + * Sets the item being dispensed. + * + * @param item the item being dispensed + */ + public void setItem(ItemStack item) { + this.item = item; + } + + /** + * Gets the velocity. + *

    + * Note: Modifying the returned Vector will not change the velocity, you + * must use {@link #setVelocity(Vector)} instead. + * + * @return A Vector for the dispensed item's velocity + */ + public Vector getVelocity() { + return velocity.clone(); + } + + /** + * Sets the velocity of the item being dispensed. + * + * @param vel the velocity of the item being dispensed + */ + public void setVelocity(Vector vel) { + velocity = vel; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockEvent.java b/src/main/java/org/bukkit/event/block/BlockEvent.java new file mode 100644 index 00000000..24052051 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Event; + +/** + * Represents a block related event. + */ +public abstract class BlockEvent extends Event { + protected Block block; + + public BlockEvent(final Block theBlock) { + block = theBlock; + } + + /** + * Gets the block involved in this event. + * + * @return The Block which block is involved in this event + */ + public final Block getBlock() { + return block; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockExpEvent.java b/src/main/java/org/bukkit/event/block/BlockExpEvent.java new file mode 100644 index 00000000..08636a29 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockExpEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.HandlerList; + +/** + * An event that's called when a block yields experience. + */ +public class BlockExpEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + private int exp; + + public BlockExpEvent(Block block, int exp) { + super(block); + + this.exp = exp; + } + + /** + * Get the experience dropped by the block after the event has processed + * + * @return The experience to drop + */ + public int getExpToDrop() { + return exp; + } + + /** + * Set the amount of experience dropped by the block after the event has + * processed + * + * @param exp 1 or higher to drop experience, else nothing will drop + */ + public void setExpToDrop(int exp) { + this.exp = exp; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java b/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java new file mode 100644 index 00000000..5f15e299 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java @@ -0,0 +1,69 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import java.util.List; + +/** + * Called when a block explodes + */ +public class BlockExplodeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final List blocks; + private float yield; + + public BlockExplodeEvent(final Block what, final List blocks, final float yield) { + super(what); + this.blocks = blocks; + this.yield = yield; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the list of blocks that would have been removed or were removed + * from the explosion event. + * + * @return All blown-up blocks + */ + public List blockList() { + return blocks; + } + + /** + * Returns the percentage of blocks to drop from this explosion + * + * @return The yield. + */ + public float getYield() { + return yield; + } + + /** + * Sets the percentage of blocks to drop from this explosion + * + * @param yield The new yield percentage + */ + public void setYield(float yield) { + this.yield = yield; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockFadeEvent.java b/src/main/java/org/bukkit/event/block/BlockFadeEvent.java new file mode 100644 index 00000000..673bc5f6 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockFadeEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a block fades, melts or disappears based on world conditions + *

    + * Examples: + *

      + *
    • Snow melting due to being near a light source. + *
    • Ice melting due to being near a light source. + *
    • Fire burning out after time, without destroying fuel block. + *
    + *

    + * If a Block Fade event is cancelled, the block will not fade, melt or + * disappear. + */ +public class BlockFadeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final BlockState newState; + + public BlockFadeEvent(final Block block, final BlockState newState) { + super(block); + this.newState = newState; + this.cancelled = false; + } + + /** + * Gets the state of the block that will be fading, melting or + * disappearing. + * + * @return The block state of the block that will be fading, melting or + * disappearing + */ + public BlockState getNewState() { + return newState; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockFormEvent.java b/src/main/java/org/bukkit/event/block/BlockFormEvent.java new file mode 100644 index 00000000..e765a4ea --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockFormEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.HandlerList; + +/** + * Called when a block is formed or spreads based on world conditions. + *

    + * Use {@link BlockSpreadEvent} to catch blocks that actually spread and don't + * just "randomly" form. + *

    + * Examples: + *

      + *
    • Snow forming due to a snow storm. + *
    • Ice forming in a snowy Biome like Taiga or Tundra. + *
    • Obsidian / Cobblestone forming due to contact with water. + *
    • Concrete forming due to mixing of concrete powder and water. + *
    + *

    + * If a Block Form event is cancelled, the block will not be formed. + * + * @see BlockSpreadEvent + */ +public class BlockFormEvent extends BlockGrowEvent { + private static final HandlerList handlers = new HandlerList(); + + public BlockFormEvent(final Block block, final BlockState newState) { + super(block, newState); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockFromToEvent.java b/src/main/java/org/bukkit/event/block/BlockFromToEvent.java new file mode 100644 index 00000000..f976bea4 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockFromToEvent.java @@ -0,0 +1,71 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Represents events with a source block and a destination block, currently + * only applies to liquid (lava and water) and teleporting dragon eggs. + *

    + * If a Block From To event is cancelled, the block will not move (the liquid + * will not flow). + */ +public class BlockFromToEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Block to; + protected BlockFace face; + protected boolean cancel; + + public BlockFromToEvent(final Block block, final BlockFace face) { + super(block); + this.face = face; + this.cancel = false; + } + + public BlockFromToEvent(final Block block, final Block toBlock) { + super(block); + this.to = toBlock; + this.face = BlockFace.SELF; + this.cancel = false; + } + + /** + * Gets the BlockFace that the block is moving to. + * + * @return The BlockFace that the block is moving to + */ + public BlockFace getFace() { + return face; + } + + /** + * Convenience method for getting the faced Block. + * + * @return The faced Block + */ + public Block getToBlock() { + if (to == null) { + to = block.getRelative(face); + } + return to; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockGrowEvent.java b/src/main/java/org/bukkit/event/block/BlockGrowEvent.java new file mode 100644 index 00000000..2a959fd6 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockGrowEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a block grows naturally in the world. + *

    + * Examples: + *

      + *
    • Wheat + *
    • Sugar Cane + *
    • Cactus + *
    • Watermelon + *
    • Pumpkin + *
    + *

    + * If a Block Grow event is cancelled, the block will not grow. + */ +public class BlockGrowEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final BlockState newState; + private boolean cancelled = false; + + public BlockGrowEvent(final Block block, final BlockState newState) { + super(block); + this.newState = newState; + } + + /** + * Gets the state of the block where it will form or spread to. + * + * @return The block state for this events block + */ + public BlockState getNewState() { + return newState; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java b/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java new file mode 100644 index 00000000..b7435608 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java @@ -0,0 +1,129 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a block is ignited. If you want to catch when a Player places + * fire, you need to use {@link BlockPlaceEvent}. + *

    + * If a Block Ignite event is cancelled, the block will not be ignited. + */ +public class BlockIgniteEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final IgniteCause cause; + private final Entity ignitingEntity; + private final Block ignitingBlock; + private boolean cancel; + + public BlockIgniteEvent(final Block theBlock, final IgniteCause cause, final Entity ignitingEntity) { + this(theBlock, cause, ignitingEntity, null); + } + + public BlockIgniteEvent(final Block theBlock, final IgniteCause cause, final Block ignitingBlock) { + this(theBlock, cause, null, ignitingBlock); + } + + public BlockIgniteEvent(final Block theBlock, final IgniteCause cause, final Entity ignitingEntity, final Block ignitingBlock) { + super(theBlock); + this.cause = cause; + this.ignitingEntity = ignitingEntity; + this.ignitingBlock = ignitingBlock; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the cause of block ignite. + * + * @return An IgniteCause value detailing the cause of block ignition + */ + public IgniteCause getCause() { + return cause; + } + + /** + * Gets the player who ignited this block + * + * @return The Player that placed/ignited the fire block, or null if not ignited by a Player. + */ + public Player getPlayer() { + if (ignitingEntity instanceof Player) { + return (Player) ignitingEntity; + } + + return null; + } + + /** + * Gets the entity who ignited this block + * + * @return The Entity that placed/ignited the fire block, or null if not ignited by a Entity. + */ + public Entity getIgnitingEntity() { + return ignitingEntity; + } + + /** + * Gets the block which ignited this block + * + * @return The Block that placed/ignited the fire block, or null if not ignited by a Block. + */ + public Block getIgnitingBlock() { + return ignitingBlock; + } + + /** + * An enum to specify the cause of the ignite + */ + public enum IgniteCause { + + /** + * Block ignition caused by lava. + */ + LAVA, + /** + * Block ignition caused by a player or dispenser using flint-and-steel. + */ + FLINT_AND_STEEL, + /** + * Block ignition caused by dynamic spreading of fire. + */ + SPREAD, + /** + * Block ignition caused by lightning. + */ + LIGHTNING, + /** + * Block ignition caused by an entity using a fireball. + */ + FIREBALL, + /** + * Block ignition caused by an Ender Crystal. + */ + ENDER_CRYSTAL, + /** + * Block ignition caused by explosion. + */ + EXPLOSION, + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java b/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java new file mode 100644 index 00000000..d16e4be7 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.block; + +import com.google.common.collect.ImmutableList; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +/** + * Fired when a single block placement action of a player triggers the + * creation of multiple blocks(e.g. placing a bed block). The block returned + * by {@link #getBlockPlaced()} and its related methods is the block where + * the placed block would exist if the placement only affected a single + * block. + */ +public class BlockMultiPlaceEvent extends BlockPlaceEvent { + private final List states; + + public BlockMultiPlaceEvent(List states, Block clicked, ItemStack itemInHand, Player thePlayer, boolean canBuild) { + super(states.get(0).getBlock(), states.get(0), clicked, itemInHand, thePlayer, canBuild); + this.states = ImmutableList.copyOf(states); + } + + /** + * Gets a list of blockstates for all blocks which were replaced by the + * placement of the new blocks. Most of these blocks will just have a + * Material type of AIR. + * + * @return immutable list of replaced BlockStates + */ + public List getReplacedBlockStates() { + return states; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java b/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java new file mode 100644 index 00000000..01a545b4 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java @@ -0,0 +1,64 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.Material; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a block physics check is called + */ +public class BlockPhysicsEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final int changed; + private boolean cancel = false; + + /** + * + * @deprecated Magic value + * @param block the block involved in this event + * @param changed the changed block's type id + */ + @Deprecated + public BlockPhysicsEvent(final Block block, final int changed) { + super(block); + this.changed = changed; + } + + /** + * Gets the type of block that changed, causing this event + * + * @return Changed block's type id + * @deprecated Magic value + */ + @Deprecated + public int getChangedTypeId() { + return changed; + } + + /** + * Gets the type of block that changed, causing this event + * + * @return Changed block's type + */ + public Material getChangedType() { + return Material.getMaterial(changed); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockPistonEvent.java b/src/main/java/org/bukkit/event/block/BlockPistonEvent.java new file mode 100644 index 00000000..074d71ca --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockPistonEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.block; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Cancellable; + +/** + * Called when a piston block is triggered + */ +public abstract class BlockPistonEvent extends BlockEvent implements Cancellable { + private boolean cancelled; + private final BlockFace direction; + + public BlockPistonEvent(final Block block, final BlockFace direction) { + super(block); + this.direction = direction; + } + + public boolean isCancelled() { + return this.cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Returns true if the Piston in the event is sticky. + * + * @return stickiness of the piston + */ + public boolean isSticky() { + return block.getType() == Material.PISTON_STICKY_BASE || block.getType() == Material.PISTON_MOVING_PIECE; + } + + /** + * Return the direction in which the piston will operate. + * + * @return direction of the piston + */ + public BlockFace getDirection() { + // Both are meh! + // return ((PistonBaseMaterial) block.getType().getNewData(block.getData())).getFacing(); + // return ((PistonBaseMaterial) block.getState().getData()).getFacing(); + return direction; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java b/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java new file mode 100644 index 00000000..682ce60a --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java @@ -0,0 +1,70 @@ +package org.bukkit.event.block; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; + +/** + * Called when a piston extends + */ +public class BlockPistonExtendEvent extends BlockPistonEvent { + private static final HandlerList handlers = new HandlerList(); + private final int length; + private List blocks; + + @Deprecated + public BlockPistonExtendEvent(final Block block, final int length, final BlockFace direction) { + super(block, direction); + + this.length = length; + } + + public BlockPistonExtendEvent(final Block block, final List blocks, final BlockFace direction) { + super(block, direction); + + this.length = blocks.size(); + this.blocks = blocks; + } + + /** + * Get the amount of blocks which will be moved while extending. + * + * @return the amount of moving blocks + * @deprecated slime blocks make the value of this method + * inaccurate due to blocks being pushed at the side + */ + @Deprecated + public int getLength() { + return this.length; + } + + /** + * Get an immutable list of the blocks which will be moved by the + * extending. + * + * @return Immutable list of the moved blocks. + */ + public List getBlocks() { + if (blocks == null) { + ArrayList tmp = new ArrayList(); + for (int i = 0; i < this.getLength(); i++) { + tmp.add(block.getRelative(getDirection(), i + 1)); + } + blocks = Collections.unmodifiableList(tmp); + } + return blocks; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java b/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java new file mode 100644 index 00000000..6d429174 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java @@ -0,0 +1,51 @@ +package org.bukkit.event.block; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; + +/** + * Called when a piston retracts + */ +public class BlockPistonRetractEvent extends BlockPistonEvent { + private static final HandlerList handlers = new HandlerList(); + private List blocks; + + public BlockPistonRetractEvent(final Block block, final List blocks, final BlockFace direction) { + super(block, direction); + + this.blocks = blocks; + } + + /** + * Gets the location where the possible moving block might be if the + * retracting piston is sticky. + * + * @return The possible location of the possibly moving block. + */ + @Deprecated + public Location getRetractLocation() { + return getBlock().getRelative(getDirection(), 2).getLocation(); + } + + /** + * Get an immutable list of the blocks which will be moved by the + * extending. + * + * @return Immutable list of the moved blocks. + */ + public List getBlocks() { + return blocks; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java b/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java new file mode 100644 index 00000000..0ee9e465 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java @@ -0,0 +1,137 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a block is placed by a player. + *

    + * If a Block Place event is cancelled, the block will not be placed. + */ +public class BlockPlaceEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected boolean cancel; + protected boolean canBuild; + protected Block placedAgainst; + protected BlockState replacedBlockState; + protected ItemStack itemInHand; + protected Player player; + protected EquipmentSlot hand; + + @Deprecated + public BlockPlaceEvent(final Block placedBlock, final BlockState replacedBlockState, final Block placedAgainst, final ItemStack itemInHand, final Player thePlayer, final boolean canBuild) { + this(placedBlock, replacedBlockState, placedAgainst, itemInHand, thePlayer, canBuild, EquipmentSlot.HAND); + } + + public BlockPlaceEvent(final Block placedBlock, final BlockState replacedBlockState, final Block placedAgainst, final ItemStack itemInHand, final Player thePlayer, final boolean canBuild, final EquipmentSlot hand) { + super(placedBlock); + this.placedAgainst = placedAgainst; + this.itemInHand = itemInHand; + this.player = thePlayer; + this.replacedBlockState = replacedBlockState; + this.canBuild = canBuild; + this.hand = hand; + cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the player who placed the block involved in this event. + * + * @return The Player who placed the block involved in this event + */ + public Player getPlayer() { + return player; + } + + /** + * Clarity method for getting the placed block. Not really needed except + * for reasons of clarity. + * + * @return The Block that was placed + */ + public Block getBlockPlaced() { + return getBlock(); + } + + /** + * Gets the BlockState for the block which was replaced. Material type air + * mostly. + * + * @return The BlockState for the block which was replaced. + */ + public BlockState getBlockReplacedState() { + return this.replacedBlockState; + } + + /** + * Gets the block that this block was placed against + * + * @return Block the block that the new block was placed against + */ + public Block getBlockAgainst() { + return placedAgainst; + } + + /** + * Gets the item in the player's hand when they placed the block. + * + * @return The ItemStack for the item in the player's hand when they + * placed the block + */ + public ItemStack getItemInHand() { + return itemInHand; + } + + /** + * Gets the hand which placed the block + * @return Main or off-hand, depending on which hand was used to place the block + */ + public EquipmentSlot getHand() { + return this.hand; + } + + /** + * Gets the value whether the player would be allowed to build here. + * Defaults to spawn if the server was going to stop them (such as, the + * player is in Spawn). Note that this is an entirely different check + * than BLOCK_CANBUILD, as this refers to a player, not universe-physics + * rule like cactus on dirt. + * + * @return boolean whether the server would allow a player to build here + */ + public boolean canBuild() { + return this.canBuild; + } + + /** + * Sets the canBuild state of this event. Set to true if you want the + * player to be able to build. + * + * @param canBuild true if you want the player to be able to build + */ + public void setBuild(boolean canBuild) { + this.canBuild = canBuild; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java b/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java new file mode 100644 index 00000000..625ec902 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java @@ -0,0 +1,55 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.HandlerList; + +/** + * Called when a redstone current changes + */ +public class BlockRedstoneEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + private final int oldCurrent; + private int newCurrent; + + public BlockRedstoneEvent(final Block block, final int oldCurrent, final int newCurrent) { + super(block); + this.oldCurrent = oldCurrent; + this.newCurrent = newCurrent; + } + + /** + * Gets the old current of this block + * + * @return The previous current + */ + public int getOldCurrent() { + return oldCurrent; + } + + /** + * Gets the new current of this block + * + * @return The new current + */ + public int getNewCurrent() { + return newCurrent; + } + + /** + * Sets the new current of this block + * + * @param newCurrent The new current to set + */ + public void setNewCurrent(int newCurrent) { + this.newCurrent = newCurrent; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java b/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java new file mode 100644 index 00000000..a1fb3634 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.HandlerList; + +/** + * Called when a block spreads based on world conditions. + *

    + * Use {@link BlockFormEvent} to catch blocks that "randomly" form instead of + * actually spread. + *

    + * Examples: + *

      + *
    • Mushrooms spreading. + *
    • Fire spreading. + *
    + *

    + * If a Block Spread event is cancelled, the block will not spread. + * + * @see BlockFormEvent + */ +public class BlockSpreadEvent extends BlockFormEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block source; + + public BlockSpreadEvent(final Block block, final Block source, final BlockState newState) { + super(block, newState); + this.source = source; + } + + /** + * Gets the source block involved in this event. + * + * @return the Block for the source block involved in this event. + */ + public Block getSource() { + return source; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java new file mode 100644 index 00000000..3a0fbba5 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java @@ -0,0 +1,110 @@ +package org.bukkit.event.block; + +import com.google.common.base.Preconditions; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private final Entity entity; + private final ChangeReason reason; + private final int oldLevel; + private int newLevel; + + public CauldronLevelChangeEvent(Block block, Entity entity, ChangeReason reason, int oldLevel, int newLevel) { + super(block); + this.entity = entity; + this.reason = reason; + this.oldLevel = oldLevel; + this.newLevel = newLevel; + } + + /** + * Get entity which did this. May be null. + * + * @return acting entity + */ + public Entity getEntity() { + return entity; + } + + public ChangeReason getReason() { + return reason; + } + + public int getOldLevel() { + return oldLevel; + } + + public int getNewLevel() { + return newLevel; + } + + public void setNewLevel(int newLevel) { + Preconditions.checkArgument(0 <= newLevel && newLevel <= 3, "Cauldron level out of bounds 0 <= %s <= 3", newLevel); + this.newLevel = newLevel; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public enum ChangeReason { + /** + * Player emptying the cauldron by filling their bucket. + */ + BUCKET_FILL, + /** + * Player filling the cauldron by emptying their bucket. + */ + BUCKET_EMPTY, + /** + * Player emptying the cauldron by filling their bottle. + */ + BOTTLE_FILL, + /** + * Player filling the cauldron by emptying their bottle. + */ + BOTTLE_EMPTY, + /** + * Player cleaning their banner. + */ + BANNER_WASH, + /** + * Player cleaning their armor. + */ + ARMOR_WASH, + /** + * Entity being extinguished. + */ + EXTINGUISH, + /** + * Evaporating due to biome dryness. + */ + EVAPORATE, + /** + * Unknown. + */ + UNKNOWN + } +} diff --git a/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java b/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java new file mode 100644 index 00000000..4ce034fe --- /dev/null +++ b/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java @@ -0,0 +1,33 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; + +/** + * Called when a block is formed by entities. + *

    + * Examples: + *

      + *
    • Snow formed by a {@link org.bukkit.entity.Snowman}. + *
    • Frosted Ice formed by the Frost Walker enchantment. + *
    + */ +public class EntityBlockFormEvent extends BlockFormEvent { + private final Entity entity; + + public EntityBlockFormEvent(final Entity entity, final Block block, final BlockState blockstate) { + super(block, blockstate); + + this.entity = entity; + } + + /** + * Get the entity that formed the block. + * + * @return Entity involved in event + */ + public Entity getEntity() { + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java b/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java new file mode 100644 index 00000000..84d8cfd9 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when leaves are decaying naturally. + *

    + * If a Leaves Decay event is cancelled, the leaves will not decay. + */ +public class LeavesDecayEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + + public LeavesDecayEvent(final Block block) { + super(block); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/NotePlayEvent.java b/src/main/java/org/bukkit/event/block/NotePlayEvent.java new file mode 100644 index 00000000..d4d43815 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/NotePlayEvent.java @@ -0,0 +1,83 @@ +package org.bukkit.event.block; + +import org.bukkit.Instrument; +import org.bukkit.Note; +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a note block is being played through player interaction or a + * redstone current. + */ +public class NotePlayEvent extends BlockEvent implements Cancellable { + + private static HandlerList handlers = new HandlerList(); + private Instrument instrument; + private Note note; + private boolean cancelled = false; + + public NotePlayEvent(Block block, Instrument instrument, Note note) { + super(block); + this.instrument = instrument; + this.note = note; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the {@link Instrument} to be used. + * + * @return the Instrument; + */ + public Instrument getInstrument() { + return instrument; + } + + /** + * Gets the {@link Note} to be played. + * + * @return the Note. + */ + public Note getNote() { + return note; + } + + /** + * Overrides the {@link Instrument} to be used. + * + * @param instrument the Instrument. Has no effect if null. + */ + public void setInstrument(Instrument instrument) { + if (instrument != null) { + this.instrument = instrument; + } + + } + + /** + * Overrides the {@link Note} to be played. + * + * @param note the Note. Has no effect if null. + */ + public void setNote(Note note) { + if (note != null) { + this.note = note; + } + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/block/SignChangeEvent.java b/src/main/java/org/bukkit/event/block/SignChangeEvent.java new file mode 100644 index 00000000..83188cf5 --- /dev/null +++ b/src/main/java/org/bukkit/event/block/SignChangeEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a sign is changed by a player. + *

    + * If a Sign Change event is cancelled, the sign will not be changed. + */ +public class SignChangeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final Player player; + private final String[] lines; + + public SignChangeEvent(final Block theBlock, final Player thePlayer, final String[] theLines) { + super(theBlock); + this.player = thePlayer; + this.lines = theLines; + } + + /** + * Gets the player changing the sign involved in this event. + * + * @return the Player involved in this event + */ + public Player getPlayer() { + return player; + } + + /** + * Gets all of the lines of text from the sign involved in this event. + * + * @return the String array for the sign's lines new text + */ + public String[] getLines() { + return lines; + } + + /** + * Gets a single line of text from the sign involved in this event. + * + * @param index index of the line to get + * @return the String containing the line of text associated with the + * provided index + * @throws IndexOutOfBoundsException thrown when the provided index is {@literal > 3 + * or < 0} + */ + public String getLine(int index) throws IndexOutOfBoundsException { + return lines[index]; + } + + /** + * Sets a single line for the sign involved in this event + * + * @param index index of the line to set + * @param line text to set + * @throws IndexOutOfBoundsException thrown when the provided index is {@literal > 3 + * or < 0} + */ + public void setLine(int index, String line) throws IndexOutOfBoundsException { + lines[index] = line; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java b/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java new file mode 100644 index 00000000..de28f1d9 --- /dev/null +++ b/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java @@ -0,0 +1,121 @@ +package org.bukkit.event.enchantment; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an ItemStack is successfully enchanted (currently at + * enchantment table) + */ +public class EnchantItemEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block table; + private final ItemStack item; + private int level; + private boolean cancelled; + private final Map enchants; + private final Player enchanter; + private int button; + + public EnchantItemEvent(final Player enchanter, final InventoryView view, final Block table, final ItemStack item, final int level, final Map enchants, final int i) { + super(view); + this.enchanter = enchanter; + this.table = table; + this.item = item; + this.level = level; + this.enchants = new HashMap(enchants); + this.cancelled = false; + this.button = i; + } + + /** + * Gets the player enchanting the item + * + * @return enchanting player + */ + public Player getEnchanter() { + return enchanter; + } + + /** + * Gets the block being used to enchant the item + * + * @return the block used for enchanting + */ + public Block getEnchantBlock() { + return table; + } + + /** + * Gets the item to be enchanted (can be modified) + * + * @return ItemStack of item + */ + public ItemStack getItem() { + return item; + } + + /** + * Get cost in exp levels of the enchantment + * + * @return experience level cost + */ + public int getExpLevelCost() { + return level; + } + + /** + * Set cost in exp levels of the enchantment + * + * @param level - cost in levels + */ + public void setExpLevelCost(int level) { + this.level = level; + } + + /** + * Get map of enchantment (levels, keyed by type) to be added to item + * (modify map returned to change values). Note: Any enchantments not + * allowed for the item will be ignored + * + * @return map of enchantment levels, keyed by enchantment + */ + public Map getEnchantsToAdd() { + return enchants; + } + + /** + * Which button was pressed to initiate the enchanting. + * + * @return The button index (0, 1, or 2). + */ + public int whichButton() { + return button; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java b/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java new file mode 100644 index 00000000..6995b6e0 --- /dev/null +++ b/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java @@ -0,0 +1,114 @@ +package org.bukkit.event.enchantment; + +import org.bukkit.block.Block; +import org.bukkit.enchantments.EnchantmentOffer; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an ItemStack is inserted in an enchantment table - can be + * called multiple times + */ +public class PrepareItemEnchantEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block table; + private final ItemStack item; + private final EnchantmentOffer[] offers; + private final int bonus; + private boolean cancelled; + private final Player enchanter; + + public PrepareItemEnchantEvent(final Player enchanter, InventoryView view, final Block table, final ItemStack item, final EnchantmentOffer[] offers, final int bonus) { + super(view); + this.enchanter = enchanter; + this.table = table; + this.item = item; + this.offers = offers; + this.bonus = bonus; + } + + /** + * Gets the player enchanting the item + * + * @return enchanting player + */ + public Player getEnchanter() { + return enchanter; + } + + /** + * Gets the block being used to enchant the item + * + * @return the block used for enchanting + */ + public Block getEnchantBlock() { + return table; + } + + /** + * Gets the item to be enchanted. + * + * @return ItemStack of item + */ + public ItemStack getItem() { + return item; + } + + /** + * Get a list of offered experience level costs of the enchantment. + * + * @return experience level costs offered + * @deprecated Use {@link #getOffers()} instead of this method + */ + public int[] getExpLevelCostsOffered() { + int[] levelOffers = new int[offers.length]; + for (int i = 0; i < offers.length; i++) { + levelOffers[i] = offers[i] != null ? offers[i].getCost() : 0; + } + return levelOffers; + } + + /** + * Get a list of available {@link EnchantmentOffer} for the player. You can + * modify the values to change the available offers for the player. An offer + * may be null, if there isn't a enchantment offer at a specific slot. There + * are 3 slots in the enchantment table available to modify. + * + * @return list of available enchantment offers + */ + public EnchantmentOffer[] getOffers() { + return offers; + } + + /** + * Get enchantment bonus in effect - corresponds to number of bookshelves + * + * @return enchantment bonus + */ + public int getEnchantmentBonus() { + return bonus; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java b/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java new file mode 100644 index 00000000..8f84818e --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.entity; + +import java.util.List; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.HandlerList; + +/** + * Called when a lingering potion applies it's effects. Happens + * once every 5 ticks + */ +public class AreaEffectCloudApplyEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final List affectedEntities; + + public AreaEffectCloudApplyEvent(final AreaEffectCloud entity, final List affectedEntities) { + super(entity); + this.affectedEntities = affectedEntities; + } + + @Override + public AreaEffectCloud getEntity() { + return (AreaEffectCloud) entity; + } + + /** + * Retrieves a mutable list of the effected entities + *

    + * It is important to note that not every entity in this list + * is guaranteed to be effected. The cloud may die during the + * application of its effects due to the depletion of {@link AreaEffectCloud#getDurationOnUse()} + * or {@link AreaEffectCloud#getRadiusOnUse()} + * + * @return the affected entity list + */ + public List getAffectedEntities() { + return affectedEntities; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java b/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java new file mode 100644 index 00000000..c4df6922 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java @@ -0,0 +1,150 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; + +/** + * Called when a creature is spawned into a world. + *

    + * If a Creature Spawn event is cancelled, the creature will not spawn. + */ +public class CreatureSpawnEvent extends EntitySpawnEvent { + private final SpawnReason spawnReason; + + public CreatureSpawnEvent(final LivingEntity spawnee, final SpawnReason spawnReason) { + super(spawnee); + this.spawnReason = spawnReason; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the reason for why the creature is being spawned. + * + * @return A SpawnReason value detailing the reason for the creature being + * spawned + */ + public SpawnReason getSpawnReason() { + return spawnReason; + } + + /** + * An enum to specify the type of spawning + */ + public enum SpawnReason { + + /** + * When something spawns from natural means + */ + NATURAL, + /** + * When an entity spawns as a jockey of another entity (mostly spider + * jockeys) + */ + JOCKEY, + /** + * When a creature spawns due to chunk generation + */ + CHUNK_GEN, + /** + * When a creature spawns from a spawner + */ + SPAWNER, + /** + * When a creature spawns from an egg + */ + EGG, + /** + * When a creature spawns from a Spawner Egg + */ + SPAWNER_EGG, + /** + * When a creature spawns because of a lightning strike + */ + LIGHTNING, + /** + * When a snowman is spawned by being built + */ + BUILD_SNOWMAN, + /** + * When an iron golem is spawned by being built + */ + BUILD_IRONGOLEM, + /** + * When a wither boss is spawned by being built + */ + BUILD_WITHER, + /** + * When an iron golem is spawned to defend a village + */ + VILLAGE_DEFENSE, + /** + * When a zombie is spawned to invade a village + */ + VILLAGE_INVASION, + /** + * When an animal breeds to create a child + */ + BREEDING, + /** + * When a slime splits + */ + SLIME_SPLIT, + /** + * When an entity calls for reinforcements + */ + REINFORCEMENTS, + /** + * When a creature is spawned by nether portal + */ + NETHER_PORTAL, + /** + * When a creature is spawned by a dispenser dispensing an egg + */ + DISPENSE_EGG, + /** + * When a zombie infects a villager + */ + INFECTION, + /** + * When a villager is cured from infection + */ + CURED, + /** + * When an ocelot has a baby spawned along with them + */ + OCELOT_BABY, + /** + * When a silverfish spawns from a block + */ + SILVERFISH_BLOCK, + /** + * When an entity spawns as a mount of another entity (mostly chicken + * jockeys) + */ + MOUNT, + /** + * When an entity spawns as a trap for players approaching + */ + TRAP, + /** + * When an entity is spawned as a result of ender pearl usage + */ + ENDER_PEARL, + /** + * When an entity is spawned as a result of the entity it is being + * perched on jumping or being damaged + */ + SHOULDER_ENTITY, + /** + * When a creature is spawned by plugins + */ + CUSTOM, + /** + * When an entity is missing a SpawnReason + */ + DEFAULT + } +} diff --git a/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java b/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java new file mode 100644 index 00000000..b103a6ae --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java @@ -0,0 +1,93 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Creeper; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a Creeper is struck by lightning. + *

    + * If a Creeper Power event is cancelled, the Creeper will not be powered. + */ +public class CreeperPowerEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final PowerCause cause; + private LightningStrike bolt; + + public CreeperPowerEvent(final Creeper creeper, final LightningStrike bolt, final PowerCause cause) { + this(creeper, cause); + this.bolt = bolt; + } + + public CreeperPowerEvent(final Creeper creeper, final PowerCause cause) { + super(creeper); + this.cause = cause; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @Override + public Creeper getEntity() { + return (Creeper) entity; + } + + /** + * Gets the lightning bolt which is striking the Creeper. + * + * @return The Entity for the lightning bolt which is striking the Creeper + */ + public LightningStrike getLightning() { + return bolt; + } + + /** + * Gets the cause of the creeper being (un)powered. + * + * @return A PowerCause value detailing the cause of change in power. + */ + public PowerCause getCause() { + return cause; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the cause of the change in power + */ + public enum PowerCause { + + /** + * Power change caused by a lightning bolt + *

    + * Powered state: true + */ + LIGHTNING, + /** + * Power change caused by something else (probably a plugin) + *

    + * Powered state: true + */ + SET_ON, + /** + * Power change caused by something else (probably a plugin) + *

    + * Powered state: false + */ + SET_OFF + } +} diff --git a/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java b/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java new file mode 100644 index 00000000..5edd94d1 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java @@ -0,0 +1,76 @@ +package org.bukkit.event.entity; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.EnderDragon; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when an EnderDragon switches controller phase. + */ +public class EnderDragonChangePhaseEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final EnderDragon.Phase currentPhase; + private EnderDragon.Phase newPhase; + + public EnderDragonChangePhaseEvent(EnderDragon enderDragon, EnderDragon.Phase currentPhase, EnderDragon.Phase newPhase) { + super(enderDragon); + this.currentPhase = currentPhase; + this.setNewPhase(newPhase); + } + + @Override + public EnderDragon getEntity() { + return (EnderDragon) entity; + } + + /** + * Gets the current phase that the dragon is in. This method will return null + * when a dragon is first spawned and hasn't yet been assigned a phase. + * + * @return the current dragon phase + */ + public EnderDragon.Phase getCurrentPhase() { + return currentPhase; + } + + /** + * Gets the new phase that the dragon will switch to. + * + * @return the new dragon phase + */ + public EnderDragon.Phase getNewPhase() { + return newPhase; + } + + /** + * Sets the new phase for the ender dragon. + * + * @param newPhase the new dragon phase + */ + public void setNewPhase(EnderDragon.Phase newPhase) { + Validate.notNull(newPhase, "New dragon phase cannot be null"); + this.newPhase = newPhase; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java b/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java new file mode 100644 index 00000000..0c9de8e3 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when the amount of air an entity has remaining changes. + */ +public class EntityAirChangeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private int amount; + // + private boolean cancelled; + + public EntityAirChangeEvent(Entity what, int amount) { + super(what); + this.amount = amount; + } + + /** + * Gets the amount of air the entity has left (measured in ticks). + * + * @return amount of air remaining + */ + public int getAmount() { + return amount; + } + + /** + * Sets the amount of air remaining for the entity (measured in ticks. + * + * @param amount amount of air remaining + */ + public void setAmount(int amount) { + this.amount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java b/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java new file mode 100644 index 00000000..2cbbc697 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java @@ -0,0 +1,22 @@ +package org.bukkit.event.entity; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; + +/** + * Called when an {@link Entity} breaks a door + *

    + * Cancelling the event will cause the event to be delayed + */ +public class EntityBreakDoorEvent extends EntityChangeBlockEvent { + public EntityBreakDoorEvent(final LivingEntity entity, final Block targetBlock) { + super(entity, targetBlock, Material.AIR, (byte) 0); + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java b/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java new file mode 100644 index 00000000..266e6e51 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java @@ -0,0 +1,119 @@ +package org.bukkit.event.entity; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when one Entity breeds with another Entity. + */ +public class EntityBreedEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final LivingEntity mother; + private final LivingEntity father; + private final LivingEntity breeder; + private final ItemStack bredWith; + private int experience; + // + private boolean cancel; + + public EntityBreedEvent(LivingEntity child, LivingEntity mother, LivingEntity father, LivingEntity breeder, ItemStack bredWith, int experience) { + super(child); + + Validate.notNull(child, "Cannot have null child"); + Validate.notNull(mother, "Cannot have null mother"); + Validate.notNull(father, "Cannot have null father"); + + // Breeder can be null in the case of spontaneous conception + this.mother = mother; + this.father = father; + this.breeder = breeder; + this.bredWith = bredWith; + + setExperience(experience); + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the parent creating this entity. + * + * @return The "birth" parent + */ + public LivingEntity getMother() { + return mother; + } + + /** + * Gets the other parent of the newly born entity. + * + * @return the other parent + */ + public LivingEntity getFather() { + return father; + } + + /** + * Gets the Entity responsible for breeding. Breeder is null for spontaneous + * conception. + * + * @return The Entity who initiated breeding. + */ + public LivingEntity getBreeder() { + return breeder; + } + + /** + * The ItemStack that was used to initiate breeding, if present. + * + * @return ItemStack used to initiate breeding. + */ + public ItemStack getBredWith() { + return bredWith; + } + + /** + * Get the amount of experience granted by breeding. + * + * @return experience amount + */ + public int getExperience() { + return experience; + } + + /** + * Set the amount of experience granted by breeding. + * + * @param experience experience amount + */ + public void setExperience(int experience) { + Validate.isTrue(experience >= 0, "Experience cannot be negative"); + this.experience = experience; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java b/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java new file mode 100644 index 00000000..9f69ded6 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java @@ -0,0 +1,81 @@ +package org.bukkit.event.entity; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when any Entity, excluding players, changes a block. + */ +public class EntityChangeBlockEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block block; + private boolean cancel; + private final Material to; + private final byte data; + + /** + * + * @param what the Entity causing the change + * @param block the block (before the change) + * @param to the future material being changed to + * @param data the future block data + * @deprecated Magic value + */ + @Deprecated + public EntityChangeBlockEvent(final Entity what, final Block block, final Material to, final byte data) { + super(what); + this.block = block; + this.cancel = false; + this.to = to; + this.data = data; + } + + /** + * Gets the block the entity is changing + * + * @return the block that is changing + */ + public Block getBlock() { + return block; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the Material that the block is changing into + * + * @return the material that the block is changing into + */ + public Material getTo() { + return to; + } + + /** + * Gets the data for the block that would be changed into + * + * @return the data for the block that would be changed into + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java b/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java new file mode 100644 index 00000000..c84bda9e --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; + +/** + * Called when a block causes an entity to combust. + */ +public class EntityCombustByBlockEvent extends EntityCombustEvent { + private final Block combuster; + + public EntityCombustByBlockEvent(final Block combuster, final Entity combustee, final int duration) { + super(combustee, duration); + this.combuster = combuster; + } + + /** + * The combuster can be lava or a block that is on fire. + *

    + * WARNING: block may be null. + * + * @return the Block that set the combustee alight. + */ + public Block getCombuster() { + return combuster; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java new file mode 100644 index 00000000..639567bd --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; + +/** + * Called when an entity causes another entity to combust. + */ +public class EntityCombustByEntityEvent extends EntityCombustEvent { + private final Entity combuster; + + public EntityCombustByEntityEvent(final Entity combuster, final Entity combustee, final int duration) { + super(combustee, duration); + this.combuster = combuster; + } + + /** + * Get the entity that caused the combustion event. + * + * @return the Entity that set the combustee alight. + */ + public Entity getCombuster() { + return combuster; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java b/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java new file mode 100644 index 00000000..43c44829 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity combusts. + *

    + * If an Entity Combust event is cancelled, the entity will not combust. + */ +public class EntityCombustEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private int duration; + private boolean cancel; + + public EntityCombustEvent(final Entity combustee, final int duration) { + super(combustee); + this.duration = duration; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * @return the amount of time (in seconds) the combustee should be alight + * for + */ + public int getDuration() { + return duration; + } + + /** + * The number of seconds the combustee should be alight for. + *

    + * This value will only ever increase the combustion time, not decrease + * existing combustion times. + * + * @param duration the time in seconds to be alight for. + */ + public void setDuration(int duration) { + this.duration = duration; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java b/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java new file mode 100644 index 00000000..286c206d --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java @@ -0,0 +1,65 @@ +package org.bukkit.event.entity; + +import java.util.List; +import org.bukkit.PortalType; +import org.bukkit.block.BlockState; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a Living Entity creates a portal in a world. + */ +public class EntityCreatePortalEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final List blocks; + private boolean cancelled = false; + private PortalType type = PortalType.CUSTOM; + + public EntityCreatePortalEvent(final LivingEntity what, final List blocks, final PortalType type) { + super(what); + + this.blocks = blocks; + this.type = type; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets a list of all blocks associated with the portal. + * + * @return List of blocks that will be changed. + */ + public List getBlocks() { + return blocks; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the type of portal that is trying to be created. + * + * @return Type of portal. + */ + public PortalType getPortalType() { + return type; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java new file mode 100644 index 00000000..31c6a012 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.entity; + +import java.util.Map; + +import com.google.common.base.Function; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; + +/** + * Called when an entity is damaged by a block + */ +public class EntityDamageByBlockEvent extends EntityDamageEvent { + private final Block damager; + + @Deprecated + public EntityDamageByBlockEvent(final Block damager, final Entity damagee, final DamageCause cause, final double damage) { + super(damagee, cause, damage); + this.damager = damager; + } + + public EntityDamageByBlockEvent(final Block damager, final Entity damagee, final DamageCause cause, final Map modifiers, final Map> modifierFunctions) { + super(damagee, cause, modifiers, modifierFunctions); + this.damager = damager; + } + + /** + * Returns the block that damaged the player. + * + * @return Block that damaged the player + */ + public Block getDamager() { + return damager; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java new file mode 100644 index 00000000..f121786a --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java @@ -0,0 +1,33 @@ +package org.bukkit.event.entity; + +import java.util.Map; + +import com.google.common.base.Function; +import org.bukkit.entity.Entity; + +/** + * Called when an entity is damaged by an entity + */ +public class EntityDamageByEntityEvent extends EntityDamageEvent { + private final Entity damager; + + @Deprecated + public EntityDamageByEntityEvent(final Entity damager, final Entity damagee, final DamageCause cause, final double damage) { + super(damagee, cause, damage); + this.damager = damager; + } + + public EntityDamageByEntityEvent(final Entity damager, final Entity damagee, final DamageCause cause, final Map modifiers, final Map> modifierFunctions) { + super(damagee, cause, modifiers, modifierFunctions); + this.damager = damager; + } + + /** + * Returns the entity that damaged the defender. + * + * @return Entity that damaged the defender. + */ + public Entity getDamager() { + return damager; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java new file mode 100644 index 00000000..6e5b93cd --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java @@ -0,0 +1,425 @@ +package org.bukkit.event.entity; + +import java.util.EnumMap; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; + +/** + * Stores data for damage events + */ +public class EntityDamageEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private static final DamageModifier[] MODIFIERS = DamageModifier.values(); + private static final Function ZERO = Functions.constant(-0.0); + private final Map modifiers; + private final Map> modifierFunctions; + private final Map originals; + private boolean cancelled; + private final DamageCause cause; + + @Deprecated + public EntityDamageEvent(final Entity damagee, final DamageCause cause, final double damage) { + this(damagee, cause, new EnumMap(ImmutableMap.of(DamageModifier.BASE, damage)), new EnumMap>(ImmutableMap.of(DamageModifier.BASE, ZERO))); + } + + public EntityDamageEvent(final Entity damagee, final DamageCause cause, final Map modifiers, final Map> modifierFunctions) { + super(damagee); + Validate.isTrue(modifiers.containsKey(DamageModifier.BASE), "BASE DamageModifier missing"); + Validate.isTrue(!modifiers.containsKey(null), "Cannot have null DamageModifier"); + Validate.noNullElements(modifiers.values(), "Cannot have null modifier values"); + Validate.isTrue(modifiers.keySet().equals(modifierFunctions.keySet()), "Must have a modifier function for each DamageModifier"); + Validate.noNullElements(modifierFunctions.values(), "Cannot have null modifier function"); + this.originals = new EnumMap(modifiers); + this.cause = cause; + this.modifiers = modifiers; + this.modifierFunctions = modifierFunctions; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the original damage for the specified modifier, as defined at this + * event's construction. + * + * @param type the modifier + * @return the original damage + * @throws IllegalArgumentException if type is null + */ + public double getOriginalDamage(DamageModifier type) throws IllegalArgumentException { + final Double damage = originals.get(type); + if (damage != null) { + return damage; + } + if (type == null) { + throw new IllegalArgumentException("Cannot have null DamageModifier"); + } + return 0; + } + + /** + * Sets the damage for the specified modifier. + * + * @param type the damage modifier + * @param damage the scalar value of the damage's modifier + * @see #getFinalDamage() + * @throws IllegalArgumentException if type is null + * @throws UnsupportedOperationException if the caller does not support + * the particular DamageModifier, or to rephrase, when {@link + * #isApplicable(DamageModifier)} returns false + */ + public void setDamage(DamageModifier type, double damage) throws IllegalArgumentException, UnsupportedOperationException { + if (!modifiers.containsKey(type)) { + throw type == null ? new IllegalArgumentException("Cannot have null DamageModifier") : new UnsupportedOperationException(type + " is not applicable to " + getEntity()); + } + modifiers.put(type, damage); + } + + /** + * Gets the damage change for some modifier + * + * @param type the damage modifier + * @return The raw amount of damage caused by the event + * @throws IllegalArgumentException if type is null + * @see DamageModifier#BASE + */ + public double getDamage(DamageModifier type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null DamageModifier"); + final Double damage = modifiers.get(type); + return damage == null ? 0 : damage; + } + + /** + * This checks to see if a particular modifier is valid for this event's + * caller, such that, {@link #setDamage(DamageModifier, double)} will not + * throw an {@link UnsupportedOperationException}. + *

    + * {@link DamageModifier#BASE} is always applicable. + * + * @param type the modifier + * @return true if the modifier is supported by the caller, false otherwise + * @throws IllegalArgumentException if type is null + */ + public boolean isApplicable(DamageModifier type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null DamageModifier"); + return modifiers.containsKey(type); + } + + /** + * Gets the raw amount of damage caused by the event + * + * @return The raw amount of damage caused by the event + * @see DamageModifier#BASE + */ + public double getDamage() { + return getDamage(DamageModifier.BASE); + } + + /** + * Gets the amount of damage caused by the event after all damage + * reduction is applied. + * + * @return the amount of damage caused by the event + */ + public final double getFinalDamage() { + double damage = 0; + for (DamageModifier modifier : MODIFIERS) { + damage += getDamage(modifier); + } + return damage; + } + + /** + * Sets the raw amount of damage caused by the event. + *

    + * For compatibility this also recalculates the modifiers and scales + * them by the difference between the modifier for the previous damage + * value and the new one. + * + * @param damage The raw amount of damage caused by the event + */ + public void setDamage(double damage) { + // These have to happen in the same order as the server calculates them, keep the enum sorted + double remaining = damage; + double oldRemaining = getDamage(DamageModifier.BASE); + for (DamageModifier modifier : MODIFIERS) { + if (!isApplicable(modifier)) { + continue; + } + + Function modifierFunction = modifierFunctions.get(modifier); + double newVanilla = modifierFunction.apply(remaining); + double oldVanilla = modifierFunction.apply(oldRemaining); + double difference = oldVanilla - newVanilla; + + // Don't allow value to cross zero, assume zero values should be negative + double old = getDamage(modifier); + if (old > 0) { + setDamage(modifier, Math.max(0, old - difference)); + } else { + setDamage(modifier, Math.min(0, old - difference)); + } + remaining += newVanilla; + oldRemaining += oldVanilla; + } + + setDamage(DamageModifier.BASE, damage); + } + + /** + * Gets the cause of the damage. + * + * @return A DamageCause value detailing the cause of the damage. + */ + public DamageCause getCause() { + return cause; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the types of modifier + * + * @deprecated This API is responsible for a large number of implementation + * problems and is in general unsustainable to maintain. It is likely to be + * removed very soon in a subsequent release. Please see + * https://www.spigotmc.org/threads/194446/ for more information. + */ + @Deprecated + public enum DamageModifier { + /** + * This represents the amount of damage being done, also known as the + * raw {@link EntityDamageEvent#getDamage()}. + */ + BASE, + /** + * This represents the damage reduced by a wearing a helmet when hit + * by a falling block. + */ + HARD_HAT, + /** + * This represents the damage reduction caused by blocking, only present for + * {@link Player Players}. + */ + BLOCKING, + /** + * This represents the damage reduction caused by wearing armor. + */ + ARMOR, + /** + * This represents the damage reduction caused by the Resistance potion effect. + */ + RESISTANCE, + /** + * This represents the damage reduction caused by the combination of: + *

      + *
    • + * Armor enchantments + *
    • + * Witch's potion resistance + *
    • + *
    + */ + MAGIC, + /** + * This represents the damage reduction caused by the absorption potion + * effect. + */ + ABSORPTION, + ; + } + + /** + * An enum to specify the cause of the damage + */ + public enum DamageCause { + + /** + * Damage caused when an entity contacts a block such as a Cactus. + *

    + * Damage: 1 (Cactus) + */ + CONTACT, + /** + * Damage caused when an entity attacks another entity. + *

    + * Damage: variable + */ + ENTITY_ATTACK, + /** + * Damage caused when an entity attacks another entity in a sweep attack. + *

    + * Damage: variable + */ + ENTITY_SWEEP_ATTACK, + /** + * Damage caused when attacked by a projectile. + *

    + * Damage: variable + */ + PROJECTILE, + /** + * Damage caused by being put in a block + *

    + * Damage: 1 + */ + SUFFOCATION, + /** + * Damage caused when an entity falls a distance greater than 3 blocks + *

    + * Damage: fall height - 3.0 + */ + FALL, + /** + * Damage caused by direct exposure to fire + *

    + * Damage: 1 + */ + FIRE, + /** + * Damage caused due to burns caused by fire + *

    + * Damage: 1 + */ + FIRE_TICK, + /** + * Damage caused due to a snowman melting + *

    + * Damage: 1 + */ + MELTING, + /** + * Damage caused by direct exposure to lava + *

    + * Damage: 4 + */ + LAVA, + /** + * Damage caused by running out of air while in water + *

    + * Damage: 2 + */ + DROWNING, + /** + * Damage caused by being in the area when a block explodes. + *

    + * Damage: variable + */ + BLOCK_EXPLOSION, + /** + * Damage caused by being in the area when an entity, such as a + * Creeper, explodes. + *

    + * Damage: variable + */ + ENTITY_EXPLOSION, + /** + * Damage caused by falling into the void + *

    + * Damage: 4 for players + */ + VOID, + /** + * Damage caused by being struck by lightning + *

    + * Damage: 5 + */ + LIGHTNING, + /** + * Damage caused by committing suicide using the command "/kill" + *

    + * Damage: 1000 + */ + SUICIDE, + /** + * Damage caused by starving due to having an empty hunger bar + *

    + * Damage: 1 + */ + STARVATION, + /** + * Damage caused due to an ongoing poison effect + *

    + * Damage: 1 + */ + POISON, + /** + * Damage caused by being hit by a damage potion or spell + *

    + * Damage: variable + */ + MAGIC, + /** + * Damage caused by Wither potion effect + */ + WITHER, + /** + * Damage caused by being hit by a falling block which deals damage + *

    + * Note: Not every block deals damage + *

    + * Damage: variable + */ + FALLING_BLOCK, + /** + * Damage caused in retaliation to another attack by the Thorns + * enchantment. + *

    + * Damage: 1-4 (Thorns) + */ + THORNS, + /** + * Damage caused by a dragon breathing fire. + *

    + * Damage: variable + */ + DRAGON_BREATH, + /** + * Custom damage. + *

    + * Damage: variable + */ + CUSTOM, + /** + * Damage caused when an entity runs into a wall. + *

    + * Damage: variable + */ + FLY_INTO_WALL, + /** + * Damage caused when an entity steps on {@link Material#MAGMA}. + *

    + * Damage: 1 + */ + HOT_FLOOR, + /** + * Damage caused when an entity is colliding with too many entities due + * to the maxEntityCramming game rule. + *

    + * Damage: 6 + */ + CRAMMING + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java b/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java new file mode 100644 index 00000000..ab9e81fd --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java @@ -0,0 +1,72 @@ +package org.bukkit.event.entity; + +import java.util.List; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Thrown whenever a LivingEntity dies + */ +public class EntityDeathEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final List drops; + private int dropExp = 0; + + public EntityDeathEvent(final LivingEntity entity, final List drops) { + this(entity, drops, 0); + } + + public EntityDeathEvent(final LivingEntity what, final List drops, final int droppedExp) { + super(what); + this.drops = drops; + this.dropExp = droppedExp; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets how much EXP should be dropped from this death. + *

    + * This does not indicate how much EXP should be taken from the entity in + * question, merely how much should be created after its death. + * + * @return Amount of EXP to drop. + */ + public int getDroppedExp() { + return dropExp; + } + + /** + * Sets how much EXP should be dropped from this death. + *

    + * This does not indicate how much EXP should be taken from the entity in + * question, merely how much should be created after its death. + * + * @param exp Amount of EXP to drop. + */ + public void setDroppedExp(int exp) { + this.dropExp = exp; + } + + /** + * Gets all the items which will drop when the entity dies + * + * @return Items to drop when the entity dies + */ + public List getDrops() { + return drops; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityEvent.java new file mode 100644 index 00000000..c9a4ab30 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; + +/** + * Represents an Entity-related event + */ +public abstract class EntityEvent extends Event { + protected Entity entity; + + public EntityEvent(final Entity what) { + entity = what; + } + + /** + * Returns the Entity involved in this event + * + * @return Entity who is involved in this event + */ + public Entity getEntity() { + return entity; + } + + /** + * Gets the EntityType of the Entity involved in this event. + * + * @return EntityType of the Entity involved in this event + */ + public EntityType getEntityType() { + return entity.getType(); + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java b/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java new file mode 100644 index 00000000..287035d1 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java @@ -0,0 +1,85 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import java.util.List; + +/** + * Called when an entity explodes + */ +public class EntityExplodeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final Location location; + private final List blocks; + private float yield; + + public EntityExplodeEvent(final Entity what, final Location location, final List blocks, final float yield) { + super(what); + this.location = location; + this.blocks = blocks; + this.yield = yield; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the list of blocks that would have been removed or were removed + * from the explosion event. + * + * @return All blown-up blocks + */ + public List blockList() { + return blocks; + } + + /** + * Returns the location where the explosion happened. + *

    + * It is not possible to get this value from the Entity as the Entity no + * longer exists in the world. + * + * @return The location of the explosion + */ + public Location getLocation() { + return location; + } + + /** + * Returns the percentage of blocks to drop from this explosion + * + * @return The yield. + */ + public float getYield() { + return yield; + } + + /** + * Sets the percentage of blocks to drop from this explosion + * + * @param yield The new yield percentage + */ + public void setYield(float yield) { + this.yield = yield; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java b/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java new file mode 100644 index 00000000..1c4e1003 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity interacts with an object + */ +public class EntityInteractEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Block block; + private boolean cancelled; + + public EntityInteractEvent(final Entity entity, final Block block) { + super(entity); + this.block = block; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Returns the involved block + * + * @return the block clicked with this item. + */ + public Block getBlock() { + return block; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java b/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java new file mode 100644 index 00000000..20f2d538 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a entity picks an item up from the ground + */ +public class EntityPickupItemEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item item; + private boolean cancel = false; + private final int remaining; + + public EntityPickupItemEvent(final LivingEntity entity, final Item item, final int remaining) { + super(entity); + this.item = item; + this.remaining = remaining; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the Item picked up by the entity. + * + * @return Item + */ + public Item getItem() { + return item; + } + + /** + * Gets the amount remaining on the ground, if any + * + * @return amount remaining on the ground + */ + public int getRemaining() { + return remaining; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java b/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java new file mode 100644 index 00000000..87d57b01 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.Location; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity comes into contact with a portal + */ +public class EntityPortalEnterEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location location; + + public EntityPortalEnterEvent(final Entity entity, final Location location) { + super(entity); + this.location = location; + } + + /** + * Gets the portal block the entity is touching + * + * @return The portal block the entity is touching + */ + public Location getLocation() { + return location; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java b/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java new file mode 100644 index 00000000..835c0548 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java @@ -0,0 +1,82 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.TravelAgent; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; + +/** + * Called when a non-player entity is about to teleport because it is in + * contact with a portal. + *

    + * For players see {@link org.bukkit.event.player.PlayerPortalEvent} + */ +public class EntityPortalEvent extends EntityTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean useTravelAgent = true; + protected TravelAgent travelAgent; + + public EntityPortalEvent(final Entity entity, final Location from, final Location to, final TravelAgent pta) { + super(entity, from, to); + this.travelAgent = pta; + } + + /** + * Sets whether or not the Travel Agent will be used. + *

    + * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

    + * If this is set to false, the {@link #getEntity()} will only be + * teleported to the {@link #getTo()} Location. + * + * @param useTravelAgent whether to use the Travel Agent + */ + public void useTravelAgent(boolean useTravelAgent) { + this.useTravelAgent = useTravelAgent; + } + + /** + * Gets whether or not the Travel Agent will be used. + *

    + * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

    + * If this is set to false, the {@link #getEntity()} will only be + * teleported to the {@link #getTo()} Location. + * + * @return whether to use the Travel Agent + */ + public boolean useTravelAgent() { + return useTravelAgent; + } + + /** + * Gets the Travel Agent used (or not) in this event. + * + * @return the Travel Agent used (or not) in this event + */ + public TravelAgent getPortalTravelAgent() { + return this.travelAgent; + } + + /** + * Sets the Travel Agent used (or not) in this event. + * + * @param travelAgent the Travel Agent used (or not) in this event + */ + public void setPortalTravelAgent(TravelAgent travelAgent) { + this.travelAgent = travelAgent; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java b/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java new file mode 100644 index 00000000..41edef6b --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.util.Vector; + +/** + * Called before an entity exits a portal. + *

    + * This event allows you to modify the velocity of the entity after they have + * successfully exited the portal. + */ +public class EntityPortalExitEvent extends EntityTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + private Vector before; + private Vector after; + + public EntityPortalExitEvent(final Entity entity, final Location from, final Location to, final Vector before, final Vector after) { + super(entity, from, to); + this.before = before; + this.after = after; + } + + /** + * Gets a copy of the velocity that the entity has before entering the + * portal. + * + * @return velocity of entity before entering the portal + */ + public Vector getBefore() { + return this.before.clone(); + } + + /** + * Gets a copy of the velocity that the entity will have after exiting the + * portal. + * + * @return velocity of entity after exiting the portal + */ + public Vector getAfter() { + return this.after.clone(); + } + + /** + * Sets the velocity that the entity will have after exiting the portal. + * + * @param after the velocity after exiting the portal + */ + public void setAfter(Vector after) { + this.after = after.clone(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java b/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java new file mode 100644 index 00000000..976b80b7 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java @@ -0,0 +1,113 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Stores data for health-regain events + */ +public class EntityRegainHealthEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private double amount; + private final RegainReason regainReason; + + public EntityRegainHealthEvent(final Entity entity, final double amount, final RegainReason regainReason) { + super(entity); + this.amount = amount; + this.regainReason = regainReason; + } + + /** + * Gets the amount of regained health + * + * @return The amount of health regained + */ + public double getAmount() { + return amount; + } + + /** + * Sets the amount of regained health + * + * @param amount the amount of health the entity will regain + */ + public void setAmount(double amount) { + this.amount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the reason for why the entity is regaining health + * + * @return A RegainReason detailing the reason for the entity regaining + * health + */ + public RegainReason getRegainReason() { + return regainReason; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the type of health regaining that is occurring + */ + public enum RegainReason { + + /** + * When a player regains health from regenerating due to Peaceful mode + * (difficulty=0) + */ + REGEN, + /** + * When a player regains health from regenerating due to their hunger + * being satisfied + */ + SATIATED, + /** + * When a player regains health from eating consumables + */ + EATING, + /** + * When an ender dragon regains health from an ender crystal + */ + ENDER_CRYSTAL, + /** + * When a player is healed by a potion or spell + */ + MAGIC, + /** + * When a player is healed over time by a potion or spell + */ + MAGIC_REGEN, + /** + * When a wither is filling its health during spawning + */ + WITHER_SPAWN, + /** + * When an entity is damaged by the Wither potion effect + */ + WITHER, + /** + * Any other reason not covered by the reasons above + */ + CUSTOM + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java b/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java new file mode 100644 index 00000000..17cd6657 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity dies and may have the opportunity to be resurrected. + * Will be called in a cancelled state if the entity does not have a totem + * equipped. + */ +public class EntityResurrectEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private boolean cancelled; + + public EntityResurrectEvent(LivingEntity what) { + super(what); + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java b/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java new file mode 100644 index 00000000..f8c91a13 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a LivingEntity shoots a bow firing an arrow + */ +public class EntityShootBowEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack bow; + private Entity projectile; + private final float force; + private boolean cancelled; + + public EntityShootBowEvent(final LivingEntity shooter, final ItemStack bow, final Projectile projectile, final float force) { + super(shooter); + this.bow = bow; + this.projectile = projectile; + this.force = force; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the bow ItemStack used to fire the arrow. + * + * @return the bow involved in this event + */ + public ItemStack getBow() { + return bow; + } + + /** + * Gets the projectile which will be launched by this event + * + * @return the launched projectile + */ + public Entity getProjectile() { + return projectile; + } + + /** + * Replaces the projectile which will be launched + * + * @param projectile the new projectile + */ + public void setProjectile(Entity projectile) { + this.projectile = projectile; + } + + /** + * Gets the force the arrow was launched with + * + * @return bow shooting force, up to 1.0 + */ + public float getForce() { + return force; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java b/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java new file mode 100644 index 00000000..5dcf98f3 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity is spawned into a world. + *

    + * If an Entity Spawn event is cancelled, the entity will not spawn. + */ +public class EntitySpawnEvent extends EntityEvent implements org.bukkit.event.Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + + public EntitySpawnEvent(final Entity spawnee) { + super(spawnee); + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the location at which the entity is spawning. + * + * @return The location at which the entity is spawning + */ + public Location getLocation() { + return getEntity().getLocation(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityTameEvent.java b/src/main/java/org/bukkit/event/entity/EntityTameEvent.java new file mode 100644 index 00000000..f105817a --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityTameEvent.java @@ -0,0 +1,51 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a LivingEntity is tamed + */ +public class EntityTameEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final AnimalTamer owner; + + public EntityTameEvent(final LivingEntity entity, final AnimalTamer owner) { + super(entity); + this.owner = owner; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the owning AnimalTamer + * + * @return the owning AnimalTamer + */ + public AnimalTamer getOwner() { + return owner; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java b/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java new file mode 100644 index 00000000..dd26e746 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java @@ -0,0 +1,155 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a creature targets or untargets another entity + */ +public class EntityTargetEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Entity target; + private final TargetReason reason; + + public EntityTargetEvent(final Entity entity, final Entity target, final TargetReason reason) { + super(entity); + this.target = target; + this.reason = reason; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the reason for the targeting + * + * @return The reason + */ + public TargetReason getReason() { + return reason; + } + + /** + * Get the entity that this is targeting. + *

    + * This will be null in the case that the event is called when the mob + * forgets its target. + * + * @return The entity + */ + public Entity getTarget() { + return target; + } + + /** + * Set the entity that you want the mob to target instead. + *

    + * It is possible to be null, null will cause the entity to be + * target-less. + *

    + * This is different from cancelling the event. Cancelling the event will + * cause the entity to keep an original target, while setting to be null + * will cause the entity to be reset. + * + * @param target The entity to target + */ + public void setTarget(Entity target) { + this.target = target; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the reason for the targeting + */ + public enum TargetReason { + + /** + * When the entity's target has died, and so it no longer targets it + */ + TARGET_DIED, + /** + * When the entity doesn't have a target, so it attacks the nearest + * player + */ + CLOSEST_PLAYER, + /** + * When the target attacks the entity, so entity targets it + */ + TARGET_ATTACKED_ENTITY, + /** + * When the target attacks a fellow pig zombie, so the whole group + * will target him with this reason. + */ + PIG_ZOMBIE_TARGET, + /** + * When the target is forgotten for whatever reason. + *

    + * Currently only occurs in with spiders when there is a high + * brightness. + */ + FORGOT_TARGET, + /** + * When the target attacks the owner of the entity, so the entity + * targets it. + */ + TARGET_ATTACKED_OWNER, + /** + * When the owner of the entity attacks the target attacks, so the + * entity targets it. + */ + OWNER_ATTACKED_TARGET, + /** + * When the entity has no target, so the entity randomly chooses one. + */ + RANDOM_TARGET, + /** + * When an entity selects a target while defending a village. + */ + DEFEND_VILLAGE, + /** + * When the target attacks a nearby entity of the same type, so the entity targets it + */ + TARGET_ATTACKED_NEARBY_ENTITY, + /** + * When a zombie targeting an entity summons reinforcements, so the reinforcements target the same entity + */ + REINFORCEMENT_TARGET, + /** + * When an entity targets another entity after colliding with it. + */ + COLLISION, + /** + * For custom calls to the event. + */ + CUSTOM, + /** + * When the entity doesn't have a target, so it attacks the nearest + * entity + */ + CLOSEST_ENTITY, + /** + * When another entity tempts this entity by having a desired item such + * as wheat in it's hand. + */ + TEMPT, + /** + * A currently unknown reason for the entity changing target. + */ + UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java new file mode 100644 index 00000000..cd9aea1c --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; + +/** + * Called when an Entity targets a {@link LivingEntity} and can only target + * LivingEntity's. + */ +public class EntityTargetLivingEntityEvent extends EntityTargetEvent{ + public EntityTargetLivingEntityEvent(final Entity entity, final LivingEntity target, final TargetReason reason) { + super(entity, target, reason); + } + + public LivingEntity getTarget() { + return (LivingEntity) super.getTarget(); + } + + /** + * Set the Entity that you want the mob to target. + *

    + * It is possible to be null, null will cause the entity to be + * target-less. + *

    + * Must be a LivingEntity, or null. + * + * @param target The entity to target + */ + public void setTarget(Entity target) { + if (target == null || target instanceof LivingEntity) { + super.setTarget(target); + } + } +} diff --git a/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java b/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java new file mode 100644 index 00000000..619f8d4f --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java @@ -0,0 +1,77 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a non-player entity (such as an Enderman) tries to teleport + * from one location to another. + */ +public class EntityTeleportEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private Location from; + private Location to; + + public EntityTeleportEvent(Entity what, Location from, Location to) { + super(what); + this.from = from; + this.to = to; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location that this entity moved from + * + * @return Location this entity moved from + */ + public Location getFrom() { + return from; + } + + /** + * Sets the location that this entity moved from + * + * @param from New location this entity moved from + */ + public void setFrom(Location from) { + this.from = from; + } + + /** + * Gets the location that this entity moved to + * + * @return Location the entity moved to + */ + public Location getTo() { + return to; + } + + /** + * Sets the location that this entity moved to + * + * @param to New Location this entity moved to + */ + public void setTo(Location to) { + this.to = to; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java b/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java new file mode 100644 index 00000000..67fbf8fb --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Sent when an entity's gliding status is toggled with an Elytra. + * Examples of when this event would be called: + *

      + *
    • Player presses the jump key while in midair and using an Elytra
    • + *
    • Player lands on ground while they are gliding (with an Elytra)
    • + *
    + * This can be visually estimated by the animation in which a player turns horizontal. + */ +public class EntityToggleGlideEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private boolean cancel = false; + private final boolean isGliding; + + public EntityToggleGlideEvent(LivingEntity who, final boolean isGliding) { + super(who); + this.isGliding = isGliding; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public boolean isGliding() { + return isGliding; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java new file mode 100644 index 00000000..da7e46c3 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java @@ -0,0 +1,52 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; + +/** + * Called immediately prior to an entity being unleashed. + */ +public class EntityUnleashEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final UnleashReason reason; + + public EntityUnleashEvent(Entity entity, UnleashReason reason) { + super(entity); + this.reason = reason; + } + + /** + * Returns the reason for the unleashing. + * + * @return The reason + */ + public UnleashReason getReason() { + return reason; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public enum UnleashReason { + /** + * When the entity's leashholder has died or logged out, and so is + * unleashed + */ + HOLDER_GONE, + /** + * When the entity's leashholder attempts to unleash it + */ + PLAYER_UNLEASH, + /** + * When the entity's leashholder is more than 10 blocks away + */ + DISTANCE, + UNKNOWN; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java b/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java new file mode 100644 index 00000000..4f64424e --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.ThrownExpBottle; +import org.bukkit.event.HandlerList; + +/** + * Called when a ThrownExpBottle hits and releases experience. + */ +public class ExpBottleEvent extends ProjectileHitEvent { + private static final HandlerList handlers = new HandlerList(); + private int exp; + private boolean showEffect = true; + + public ExpBottleEvent(final ThrownExpBottle bottle, final int exp) { + super(bottle); + this.exp = exp; + } + + @Override + public ThrownExpBottle getEntity() { + return (ThrownExpBottle) entity; + } + + /** + * This method indicates if the particle effect should be shown. + * + * @return true if the effect will be shown, false otherwise + */ + public boolean getShowEffect() { + return this.showEffect; + } + + /** + * This method sets if the particle effect will be shown. + *

    + * This does not change the experience created. + * + * @param showEffect true indicates the effect will be shown, false + * indicates no effect will be shown + */ + public void setShowEffect(final boolean showEffect) { + this.showEffect = showEffect; + } + + /** + * This method retrieves the amount of experience to be created. + *

    + * The number indicates a total amount to be divided into orbs. + * + * @return the total amount of experience to be created + */ + public int getExperience() { + return exp; + } + + /** + * This method sets the amount of experience to be created. + *

    + * The number indicates a total amount to be divided into orbs. + * + * @param exp the total amount of experience to be created + */ + public void setExperience(final int exp) { + this.exp = exp; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java b/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java new file mode 100644 index 00000000..7ca6a556 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java @@ -0,0 +1,80 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Explosive; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when an entity has made a decision to explode. + */ +public class ExplosionPrimeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private float radius; + private boolean fire; + + public ExplosionPrimeEvent(final Entity what, final float radius, final boolean fire) { + super(what); + this.cancel = false; + this.radius = radius; + this.fire = fire; + } + + public ExplosionPrimeEvent(final Explosive explosive) { + this(explosive, explosive.getYield(), explosive.isIncendiary()); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the radius of the explosion + * + * @return returns the radius of the explosion + */ + public float getRadius() { + return radius; + } + + /** + * Sets the radius of the explosion + * + * @param radius the radius of the explosion + */ + public void setRadius(float radius) { + this.radius = radius; + } + + /** + * Gets whether this explosion will create fire or not + * + * @return true if this explosion will create fire + */ + public boolean getFire() { + return fire; + } + + /** + * Sets whether this explosion will create fire or not + * + * @param fire true if you want this explosion to create fire + */ + public void setFire(boolean fire) { + this.fire = fire; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java b/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java new file mode 100644 index 00000000..81b1c483 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Firework; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a firework explodes. + */ +public class FireworkExplodeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + + public FireworkExplodeEvent(final Firework what) { + super(what); + } + + @Override + public boolean isCancelled() { + return cancel; + } + + /** + * Set the cancelled state of this event. If the firework explosion is + * cancelled, the firework will still be removed, but no particles will be + * displayed. + * + * @param cancel whether to cancel or not. + */ + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public Firework getEntity() { + return (Firework) super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java b/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java new file mode 100644 index 00000000..f6e24725 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a human entity's food level changes + */ +public class FoodLevelChangeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private int level; + + public FoodLevelChangeEvent(final HumanEntity what, final int level) { + super(what); + this.level = level; + } + + @Override + public HumanEntity getEntity() { + return (HumanEntity) entity; + } + + /** + * Gets the resultant food level that the entity involved in this event + * should be set to. + *

    + * Where 20 is a full food bar and 0 is an empty one. + * + * @return The resultant food level + */ + public int getFoodLevel() { + return level; + } + + /** + * Sets the resultant food level that the entity involved in this event + * should be set to + * + * @param level the resultant food level that the entity involved in this + * event should be set to + */ + public void setFoodLevel(int level) { + if (level > 20) level = 20; + else if (level < 0) level = 0; + + this.level = level; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java b/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java new file mode 100644 index 00000000..17949136 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.entity; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.entity.AbstractHorse; + +/** + * Called when a horse jumps. + */ +public class HorseJumpEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private float power; + + public HorseJumpEvent(final AbstractHorse horse, final float power) { + super(horse); + this.power = power; + } + + public boolean isCancelled() { + return cancelled; + } + + /** + * @deprecated horse jumping was moved client side. + */ + @Deprecated + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public AbstractHorse getEntity() { + return (AbstractHorse) entity; + } + + /** + * Gets the power of the jump. + *

    + * Power is a value that defines how much of the horse's jump strength + * should be used for the jump. Power is effectively multiplied times + * the horse's jump strength to determine how high the jump is; 0 + * represents no jump strength while 1 represents full jump strength. + * Setting power to a value above 1 will use additional jump strength + * that the horse does not usually have. + *

    + * Power does not affect how high the horse is capable of jumping, only + * how much of its jumping capability will be used in this jump. To set + * the horse's overall jump strength, see {@link + * AbstractHorse#setJumpStrength(double)}. + * + * @return jump strength + */ + public float getPower() { + return power; + } + + /** + * Sets the power of the jump. + *

    + * Jump power can be set to a value above 1.0 which will increase the + * strength of this jump above the horse's actual jump strength. + *

    + * Setting the jump power to 0 will result in the jump animation still + * playing, but the horse not leaving the ground. Only canceling this + * event will result in no jump animation at all. + * + * @param power power of the jump + * @deprecated horse jumping was moved client side. + */ + @Deprecated + public void setPower(float power) { + this.power = power; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java b/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java new file mode 100644 index 00000000..23b9112f --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java @@ -0,0 +1,55 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * This event is called when a {@link Item} is removed from + * the world because it has existed for 5 minutes. + *

    + * Cancelling the event results in the item being allowed to exist for 5 more + * minutes. This behavior is not guaranteed and may change in future versions. + */ +public class ItemDespawnEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final Location location; + + public ItemDespawnEvent(final Item despawnee, final Location loc) { + super(despawnee); + location = loc; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @Override + public Item getEntity() { + return (Item) entity; + } + + /** + * Gets the location at which the item is despawning. + * + * @return The location at which the item is despawning + */ + public Location getLocation() { + return location; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java new file mode 100644 index 00000000..dadf2210 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +public class ItemMergeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Item target; + + public ItemMergeEvent(Item item, Item target) { + super(item); + this.target = target; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public Item getEntity() { + return (Item) entity; + } + + /** + * Gets the Item entity the main Item is being merged into. + * + * @return The Item being merged with + */ + public Item getTarget() { + return target; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java b/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java new file mode 100644 index 00000000..776f8e72 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java @@ -0,0 +1,23 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Item; + +/** + * Called when an item is spawned into a world + */ +public class ItemSpawnEvent extends EntitySpawnEvent { + public ItemSpawnEvent(final Item spawnee) { + super(spawnee); + } + + @Deprecated + public ItemSpawnEvent(final Item spawnee, final Location loc) { + this(spawnee); + } + + @Override + public Item getEntity() { + return (Item) entity; + } +} diff --git a/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java b/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java new file mode 100644 index 00000000..dda37d6b --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java @@ -0,0 +1,52 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.LingeringPotion; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a splash potion hits an area + */ +public class LingeringPotionSplashEvent extends ProjectileHitEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final AreaEffectCloud entity; + + public LingeringPotionSplashEvent(final ThrownPotion potion, final AreaEffectCloud entity) { + super(potion); + this.entity = entity; + } + + @Override + public LingeringPotion getEntity() { + return (LingeringPotion) super.getEntity(); + } + + /** + * Gets the AreaEffectCloud spawned + * + * @return The spawned AreaEffectCloud + */ + public AreaEffectCloud getAreaEffectCloud() { + return entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/PigZapEvent.java b/src/main/java/org/bukkit/event/entity/PigZapEvent.java new file mode 100644 index 00000000..aa80ebf1 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/PigZapEvent.java @@ -0,0 +1,64 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LightningStrike; +import org.bukkit.entity.Pig; +import org.bukkit.entity.PigZombie; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Stores data for pigs being zapped + */ +public class PigZapEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final PigZombie pigzombie; + private final LightningStrike bolt; + + public PigZapEvent(final Pig pig, final LightningStrike bolt, final PigZombie pigzombie) { + super(pig); + this.bolt = bolt; + this.pigzombie = pigzombie; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @Override + public Pig getEntity() { + return (Pig) entity; + } + + /** + * Gets the bolt which is striking the pig. + * + * @return lightning entity + */ + public LightningStrike getLightning() { + return bolt; + } + + /** + * Gets the zombie pig that will replace the pig, provided the event is + * not cancelled first. + * + * @return resulting entity + */ + public PigZombie getPigZombie() { + return pigzombie; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java b/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java new file mode 100644 index 00000000..aad03549 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java @@ -0,0 +1,161 @@ +package org.bukkit.event.entity; + +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * Thrown whenever a {@link Player} dies + */ +public class PlayerDeathEvent extends EntityDeathEvent { + private int newExp = 0; + private String deathMessage = ""; + private int newLevel = 0; + private int newTotalExp = 0; + private boolean keepLevel = false; + private boolean keepInventory = false; + + public PlayerDeathEvent(final Player player, final List drops, final int droppedExp, final String deathMessage) { + this(player, drops, droppedExp, 0, deathMessage); + } + + public PlayerDeathEvent(final Player player, final List drops, final int droppedExp, final int newExp, final String deathMessage) { + this(player, drops, droppedExp, newExp, 0, 0, deathMessage); + } + + public PlayerDeathEvent(final Player player, final List drops, final int droppedExp, final int newExp, final int newTotalExp, final int newLevel, final String deathMessage) { + super(player, drops, droppedExp); + this.newExp = newExp; + this.newTotalExp = newTotalExp; + this.newLevel = newLevel; + this.deathMessage = deathMessage; + } + + @Override + public Player getEntity() { + return (Player) entity; + } + + /** + * Set the death message that will appear to everyone on the server. + * + * @param deathMessage Message to appear to other players on the server. + */ + public void setDeathMessage(String deathMessage) { + this.deathMessage = deathMessage; + } + + /** + * Get the death message that will appear to everyone on the server. + * + * @return Message to appear to other players on the server. + */ + public String getDeathMessage() { + return deathMessage; + } + + /** + * Gets how much EXP the Player should have at respawn. + *

    + * This does not indicate how much EXP should be dropped, please see + * {@link #getDroppedExp()} for that. + * + * @return New EXP of the respawned player + */ + public int getNewExp() { + return newExp; + } + + /** + * Sets how much EXP the Player should have at respawn. + *

    + * This does not indicate how much EXP should be dropped, please see + * {@link #setDroppedExp(int)} for that. + * + * @param exp New EXP of the respawned player + */ + public void setNewExp(int exp) { + newExp = exp; + } + + /** + * Gets the Level the Player should have at respawn. + * + * @return New Level of the respawned player + */ + public int getNewLevel() { + return newLevel; + } + + /** + * Sets the Level the Player should have at respawn. + * + * @param level New Level of the respawned player + */ + public void setNewLevel(int level) { + newLevel = level; + } + + /** + * Gets the Total EXP the Player should have at respawn. + * + * @return New Total EXP of the respawned player + */ + public int getNewTotalExp() { + return newTotalExp; + } + + /** + * Sets the Total EXP the Player should have at respawn. + * + * @param totalExp New Total EXP of the respawned player + */ + public void setNewTotalExp(int totalExp) { + newTotalExp = totalExp; + } + + /** + * Gets if the Player should keep all EXP at respawn. + *

    + * This flag overrides other EXP settings + * + * @return True if Player should keep all pre-death exp + */ + public boolean getKeepLevel() { + return keepLevel; + } + + /** + * Sets if the Player should keep all EXP at respawn. + *

    + * This overrides all other EXP settings + *

    + * This doesn't prevent prevent the EXP from dropping. + * {@link #setDroppedExp(int)} should be used stop the + * EXP from dropping. + * + * @param keepLevel True to keep all current value levels + */ + public void setKeepLevel(boolean keepLevel) { + this.keepLevel = keepLevel; + } + + /** + * Sets if the Player keeps inventory on death. + * + * @param keepInventory True to keep the inventory + */ + public void setKeepInventory(boolean keepInventory) { + this.keepInventory = keepInventory; + } + + /** + * Gets if the Player keeps inventory on death. + * + * @return True if the player keeps inventory on death + */ + public boolean getKeepInventory() { + return keepInventory; + } +} diff --git a/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java b/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java new file mode 100644 index 00000000..74d458a8 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java @@ -0,0 +1,68 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called immediately prior to a creature being leashed by a player. + */ +public class PlayerLeashEntityEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity leashHolder; + private final Entity entity; + private boolean cancelled = false; + private final Player player; + + public PlayerLeashEntityEvent(Entity what, Entity leashHolder, Player leasher) { + this.leashHolder = leashHolder; + this.entity = what; + this.player = leasher; + } + + /** + * Returns the entity that is holding the leash. + * + * @return The leash holder + */ + public Entity getLeashHolder() { + return leashHolder; + } + + /** + * Returns the entity being leashed. + * + * @return The entity + */ + public Entity getEntity() { + return entity; + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + public final Player getPlayer() { + return player; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public boolean isCancelled() { + return this.cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java b/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java new file mode 100644 index 00000000..568a928c --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java @@ -0,0 +1,94 @@ +package org.bukkit.event.entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a splash potion hits an area + */ +public class PotionSplashEvent extends ProjectileHitEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Map affectedEntities; + + public PotionSplashEvent(final ThrownPotion potion, final Map affectedEntities) { + super(potion); + + this.affectedEntities = affectedEntities; + } + + @Override + public ThrownPotion getEntity() { + return (ThrownPotion) entity; + } + + /** + * Gets the potion which caused this event + * + * @return The thrown potion entity + */ + public ThrownPotion getPotion() { + return (ThrownPotion) getEntity(); + } + + /** + * Retrieves a list of all effected entities + * + * @return A fresh copy of the affected entity list + */ + public Collection getAffectedEntities() { + return new ArrayList(affectedEntities.keySet()); + } + + /** + * Gets the intensity of the potion's effects for given entity; This + * depends on the distance to the impact center + * + * @param entity Which entity to get intensity for + * @return intensity relative to maximum effect; 0.0: not affected; 1.0: + * fully hit by potion effects + */ + public double getIntensity(LivingEntity entity) { + Double intensity = affectedEntities.get(entity); + return intensity != null ? intensity : 0.0; + } + + /** + * Overwrites the intensity for a given entity + * + * @param entity For which entity to define a new intensity + * @param intensity relative to maximum effect + */ + public void setIntensity(LivingEntity entity, double intensity) { + Validate.notNull(entity, "You must specify a valid entity."); + if (intensity <= 0.0) { + affectedEntities.remove(entity); + } else { + affectedEntities.put(entity, Math.min(intensity, 1.0)); + } + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java new file mode 100644 index 00000000..35f4148b --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java @@ -0,0 +1,66 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.HandlerList; + +/** + * Called when a projectile hits an object + */ +public class ProjectileHitEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Entity hitEntity; + private final Block hitBlock; + + public ProjectileHitEvent(final Projectile projectile) { + this(projectile, null, null); + } + + public ProjectileHitEvent(final Projectile projectile, Entity hitEntity) { + this(projectile, hitEntity, null); + } + + public ProjectileHitEvent(final Projectile projectile, Block hitBlock) { + this(projectile, null, hitBlock); + } + + public ProjectileHitEvent(final Projectile projectile, Entity hitEntity, Block hitBlock) { + super(projectile); + this.hitEntity = hitEntity; + this.hitBlock = hitBlock; + } + + @Override + public Projectile getEntity() { + return (Projectile) entity; + } + + /** + * Gets the block that was hit, if it was a block that was hit. + * + * @return hit block or else null + */ + public Block getHitBlock() { + return hitBlock; + } + + /** + * Gets the entity that was hit, if it was an entity that was hit. + * + * @return hit entity or else null + */ + public Entity getHitEntity() { + return hitEntity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java b/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java new file mode 100644 index 00000000..0c9190ca --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a projectile is launched. + */ +public class ProjectileLaunchEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public ProjectileLaunchEvent(Entity what) { + super(what); + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public Projectile getEntity() { + return (Projectile) entity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java b/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java new file mode 100644 index 00000000..4c17fea3 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.entity; + +import org.bukkit.DyeColor; +import org.bukkit.entity.Sheep; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a sheep's wool is dyed + */ +public class SheepDyeWoolEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private DyeColor color; + + public SheepDyeWoolEvent(final Sheep sheep, final DyeColor color) { + super(sheep); + this.cancel = false; + this.color = color; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public Sheep getEntity() { + return (Sheep) entity; + } + + /** + * Gets the DyeColor the sheep is being dyed + * + * @return the DyeColor the sheep is being dyed + */ + public DyeColor getColor() { + return color; + } + + /** + * Sets the DyeColor the sheep is being dyed + * + * @param color the DyeColor the sheep will be dyed + */ + public void setColor(DyeColor color) { + this.color = color; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java b/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java new file mode 100644 index 00000000..e836f7b3 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java @@ -0,0 +1,41 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Sheep; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a sheep regrows its wool + */ +public class SheepRegrowWoolEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + + public SheepRegrowWoolEvent(final Sheep sheep) { + super(sheep); + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public Sheep getEntity() { + return (Sheep) entity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java b/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java new file mode 100644 index 00000000..4b995871 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a Slime splits into smaller Slimes upon death + */ +public class SlimeSplitEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private int count; + + public SlimeSplitEvent(final Slime slime, final int count) { + super(slime); + this.count = count; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public Slime getEntity() { + return (Slime) entity; + } + + /** + * Gets the amount of smaller slimes to spawn + * + * @return the amount of slimes to spawn + */ + public int getCount() { + return count; + } + + /** + * Sets how many smaller slimes will spawn on the split + * + * @param count the amount of slimes to spawn + */ + public void setCount(int count) { + this.count = count; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java b/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java new file mode 100644 index 00000000..1acb3c40 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java @@ -0,0 +1,22 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.Entity; + +/** + * Called when an entity is spawned into a world by a spawner. + *

    + * If a Spawner Spawn event is cancelled, the entity will not spawn. + */ +public class SpawnerSpawnEvent extends EntitySpawnEvent { + private final CreatureSpawner spawner; + + public SpawnerSpawnEvent(final Entity spawnee, final CreatureSpawner spawner) { + super(spawnee); + this.spawner = spawner; + } + + public CreatureSpawner getSpawner() { + return spawner; + } +} diff --git a/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java b/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java new file mode 100644 index 00000000..a63271eb --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java @@ -0,0 +1,64 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Villager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MerchantRecipe; + +/** + * Called whenever a villager acquires a new trade. + */ +public class VillagerAcquireTradeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private MerchantRecipe recipe; + + public VillagerAcquireTradeEvent(Villager what, MerchantRecipe recipe) { + super(what); + this.recipe = recipe; + } + + /** + * Get the recipe to be acquired. + * + * @return the new recipe + */ + public MerchantRecipe getRecipe() { + return recipe; + } + + /** + * Set the recipe to be acquired. + * + * @param recipe the new recipe + */ + public void setRecipe(MerchantRecipe recipe) { + this.recipe = recipe; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public Villager getEntity() { + return (Villager) super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java b/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java new file mode 100644 index 00000000..1bb39ca6 --- /dev/null +++ b/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Villager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MerchantRecipe; + +/** + * Called when a villager's trade's maximum uses is increased, due to a player's + * trade. + * + * @see MerchantRecipe#getMaxUses() + */ +public class VillagerReplenishTradeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private MerchantRecipe recipe; + private int bonus; + + public VillagerReplenishTradeEvent(Villager what, MerchantRecipe recipe, int bonus) { + super(what); + this.recipe = recipe; + this.bonus = bonus; + } + + /** + * Get the recipe to replenish. + * + * @return the replenished recipe + */ + public MerchantRecipe getRecipe() { + return recipe; + } + + /** + * Set the recipe to replenish. + * + * @param recipe the replenished recipe + */ + public void setRecipe(MerchantRecipe recipe) { + this.recipe = recipe; + } + + /** + * Get the bonus uses added. The maximum uses of the recipe will be + * increased by this number. + * + * @return the extra uses added + */ + public int getBonus() { + return bonus; + } + + /** + * Set the bonus uses added. + * + * @see VillagerReplenishTradeEvent#getBonus() + * @param bonus the extra uses added + */ + public void setBonus(int bonus) { + this.bonus = bonus; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public Villager getEntity() { + return (Villager) super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java b/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java new file mode 100644 index 00000000..c6f47492 --- /dev/null +++ b/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java @@ -0,0 +1,29 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Hanging; + +/** + * Triggered when a hanging entity is removed by an entity + */ +public class HangingBreakByEntityEvent extends HangingBreakEvent { + private final Entity remover; + + public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover) { + this(hanging, remover, RemoveCause.ENTITY); + } + + public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover, final RemoveCause cause) { + super(hanging, cause); + this.remover = remover; + } + + /** + * Gets the entity that removed the hanging entity + * + * @return the entity that removed the hanging entity + */ + public Entity getRemover() { + return remover; + } +} diff --git a/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java b/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java new file mode 100644 index 00000000..45e320a2 --- /dev/null +++ b/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java @@ -0,0 +1,71 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Hanging; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Triggered when a hanging entity is removed + */ +public class HangingBreakEvent extends HangingEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final RemoveCause cause; + + public HangingBreakEvent(final Hanging hanging, final RemoveCause cause) { + super(hanging); + this.cause = cause; + } + + /** + * Gets the cause for the hanging entity's removal + * + * @return the RemoveCause for the hanging entity's removal + */ + public RemoveCause getCause() { + return cause; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * An enum to specify the cause of the removal + */ + public enum RemoveCause { + /** + * Removed by an entity + */ + ENTITY, + /** + * Removed by an explosion + */ + EXPLOSION, + /** + * Removed by placing a block on it + */ + OBSTRUCTION, + /** + * Removed by destroying the block behind it, etc + */ + PHYSICS, + /** + * Removed by an uncategorised cause + */ + DEFAULT, + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/hanging/HangingEvent.java b/src/main/java/org/bukkit/event/hanging/HangingEvent.java new file mode 100644 index 00000000..b370afe7 --- /dev/null +++ b/src/main/java/org/bukkit/event/hanging/HangingEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Hanging; +import org.bukkit.event.Event; + +/** + * Represents a hanging entity-related event. + */ +public abstract class HangingEvent extends Event { + protected Hanging hanging; + + protected HangingEvent(final Hanging painting) { + this.hanging = painting; + } + + /** + * Gets the hanging entity involved in this event. + * + * @return the hanging entity + */ + public Hanging getEntity() { + return hanging; + } +} diff --git a/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java b/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java new file mode 100644 index 00000000..b511c555 --- /dev/null +++ b/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java @@ -0,0 +1,70 @@ +package org.bukkit.event.hanging; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Triggered when a hanging entity is created in the world + */ +public class HangingPlaceEvent extends HangingEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Player player; + private final Block block; + private final BlockFace blockFace; + + public HangingPlaceEvent(final Hanging hanging, final Player player, final Block block, final BlockFace blockFace) { + super(hanging); + this.player = player; + this.block = block; + this.blockFace = blockFace; + } + + /** + * Returns the player placing the hanging entity + * + * @return the player placing the hanging entity + */ + public Player getPlayer() { + return player; + } + + /** + * Returns the block that the hanging entity was placed on + * + * @return the block that the hanging entity was placed on + */ + public Block getBlock() { + return block; + } + + /** + * Returns the face of the block that the hanging entity was placed on + * + * @return the face of the block that the hanging entity was placed on + */ + public BlockFace getBlockFace() { + return blockFace; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/BrewEvent.java b/src/main/java/org/bukkit/event/inventory/BrewEvent.java new file mode 100644 index 00000000..e3df5bb5 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/BrewEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.BrewerInventory; + +/** + * Called when the brewing of the contents inside the Brewing Stand is + * complete. + */ +public class BrewEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private BrewerInventory contents; + private int fuelLevel; + private boolean cancelled; + + public BrewEvent(Block brewer, BrewerInventory contents, int fuelLevel) { + super(brewer); + this.contents = contents; + this.fuelLevel = fuelLevel; + } + + /** + * Gets the contents of the Brewing Stand. + * + * @return the contents + */ + public BrewerInventory getContents() { + return contents; + } + + /** + * Gets the remaining fuel level. + * + * @return the remaining fuel + */ + public int getFuelLevel() { + return fuelLevel; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java b/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java new file mode 100644 index 00000000..7e8ff9c3 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java @@ -0,0 +1,92 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an ItemStack is about to increase the fuel level of a brewing + * stand. + */ +public class BrewingStandFuelEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final ItemStack fuel; + private int fuelPower; + private boolean cancelled; + private boolean consuming = true; + + public BrewingStandFuelEvent(Block brewingStand, ItemStack fuel, int fuelPower) { + super(brewingStand); + this.fuel = fuel; + this.fuelPower = fuelPower; + } + + /** + * Gets the ItemStack of the fuel before the amount was subtracted. + * + * @return the fuel ItemStack + */ + public ItemStack getFuel() { + return fuel; + } + + /** + * Gets the fuel power for this fuel. Each unit of power can fuel one + * brewing operation. + * + * @return the fuel power for this fuel + */ + public int getFuelPower() { + return fuelPower; + } + + /** + * Sets the fuel power for this fuel. Each unit of power can fuel one + * brewing operation. + * + * @param fuelPower the fuel power for this fuel + */ + public void setFuelPower(int fuelPower) { + this.fuelPower = fuelPower; + } + + /** + * Gets whether the brewing stand's fuel will be reduced / consumed or not. + * + * @return whether the fuel will be reduced or not + */ + public boolean isConsuming() { + return consuming; + } + + /** + * Sets whether the brewing stand's fuel will be reduced / consumed or not. + * + * @param consuming whether the fuel will be reduced or not + */ + public void setConsuming(boolean consuming) { + this.consuming = consuming; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/ClickType.java b/src/main/java/org/bukkit/event/inventory/ClickType.java new file mode 100644 index 00000000..a7440aac --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/ClickType.java @@ -0,0 +1,115 @@ +package org.bukkit.event.inventory; + +/** + * What the client did to trigger this action (not the result). + */ +public enum ClickType { + + /** + * The left (or primary) mouse button. + */ + LEFT, + /** + * Holding shift while pressing the left mouse button. + */ + SHIFT_LEFT, + /** + * The right mouse button. + */ + RIGHT, + /** + * Holding shift while pressing the right mouse button. + */ + SHIFT_RIGHT, + /** + * Clicking the left mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_LEFT, + /** + * Clicking the right mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_RIGHT, + /** + * The middle mouse button, or a "scrollwheel click". + */ + MIDDLE, + /** + * One of the number keys 1-9, correspond to slots on the hotbar. + */ + NUMBER_KEY, + /** + * Pressing the left mouse button twice in quick succession. + */ + DOUBLE_CLICK, + /** + * The "Drop" key (defaults to Q). + */ + DROP, + /** + * Holding Ctrl while pressing the "Drop" key (defaults to Q). + */ + CONTROL_DROP, + /** + * Any action done with the Creative inventory open. + */ + CREATIVE, + /** + * A type of inventory manipulation not yet recognized by Bukkit. + *

    + * This is only for transitional purposes on a new Minecraft update, and + * should never be relied upon. + *

    + * Any ClickType.UNKNOWN is called on a best-effort basis. + */ + UNKNOWN, + ; + + /** + * Gets whether this ClickType represents the pressing of a key on a + * keyboard. + * + * @return true if this ClickType represents the pressing of a key + */ + public boolean isKeyboardClick() { + return (this == ClickType.NUMBER_KEY) || (this == ClickType.DROP) || (this == ClickType.CONTROL_DROP); + } + + /** + * Gets whether this ClickType represents an action that can only be + * performed by a Player in creative mode. + * + * @return true if this action requires Creative mode + */ + public boolean isCreativeAction() { + // Why use middle click? + return (this == ClickType.MIDDLE) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType represents a right click. + * + * @return true if this ClickType represents a right click + */ + public boolean isRightClick() { + return (this == ClickType.RIGHT) || (this == ClickType.SHIFT_RIGHT); + } + + /** + * Gets whether this ClickType represents a left click. + * + * @return true if this ClickType represents a left click + */ + public boolean isLeftClick() { + return (this == ClickType.LEFT) || (this == ClickType.SHIFT_LEFT) || (this == ClickType.DOUBLE_CLICK) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType indicates that the shift key was pressed + * down when the click was made. + * + * @return true if the action uses Shift. + */ + public boolean isShiftClick() { + return (this == ClickType.SHIFT_LEFT) || (this == ClickType.SHIFT_RIGHT) || (this == ClickType.CONTROL_DROP); + } +} diff --git a/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java b/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java new file mode 100644 index 00000000..dafaf41e --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java @@ -0,0 +1,35 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Recipe; + +/** + * Called when the recipe of an Item is completed inside a crafting matrix. + */ +public class CraftItemEvent extends InventoryClickEvent { + private Recipe recipe; + + public CraftItemEvent(Recipe recipe, InventoryView what, SlotType type, int slot, ClickType click, InventoryAction action) { + super(what, type, slot, click, action); + this.recipe = recipe; + } + + public CraftItemEvent(Recipe recipe, InventoryView what, SlotType type, int slot, ClickType click, InventoryAction action, int key) { + super(what, type, slot, click, action, key); + this.recipe = recipe; + } + + /** + * @return A copy of the current recipe on the crafting matrix. + */ + public Recipe getRecipe() { + return recipe; + } + + @Override + public CraftingInventory getInventory() { + return (CraftingInventory) super.getInventory(); + } +} diff --git a/src/main/java/org/bukkit/event/inventory/DragType.java b/src/main/java/org/bukkit/event/inventory/DragType.java new file mode 100644 index 00000000..72c2bed9 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/DragType.java @@ -0,0 +1,17 @@ +package org.bukkit.event.inventory; + +/** + * Represents the effect of a drag that will be applied to an Inventory in an + * InventoryDragEvent. + */ +public enum DragType { + /** + * One item from the cursor is placed in each selected slot. + */ + SINGLE, + /** + * The cursor is split evenly across all selected slots, not to exceed the + * Material's max stack size, with the remainder going to the cursor. + */ + EVEN, +} diff --git a/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java b/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java new file mode 100644 index 00000000..7cc77392 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java @@ -0,0 +1,88 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an ItemStack is successfully burned as fuel in a furnace. + */ +public class FurnaceBurnEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack fuel; + private int burnTime; + private boolean cancelled; + private boolean burning; + + public FurnaceBurnEvent(final Block furnace, final ItemStack fuel, final int burnTime) { + super(furnace); + this.fuel = fuel; + this.burnTime = burnTime; + this.cancelled = false; + this.burning = true; + } + + /** + * Gets the fuel ItemStack for this event + * + * @return the fuel ItemStack + */ + public ItemStack getFuel() { + return fuel; + } + + /** + * Gets the burn time for this fuel + * + * @return the burn time for this fuel + */ + public int getBurnTime() { + return burnTime; + } + + /** + * Sets the burn time for this fuel + * + * @param burnTime the burn time for this fuel + */ + public void setBurnTime(int burnTime) { + this.burnTime = burnTime; + } + + /** + * Gets whether the furnace's fuel is burning or not. + * + * @return whether the furnace's fuel is burning or not. + */ + public boolean isBurning() { + return this.burning; + } + + /** + * Sets whether the furnace's fuel is burning or not. + * + * @param burning true if the furnace's fuel is burning + */ + public void setBurning(boolean burning) { + this.burning = burning; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java b/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java new file mode 100644 index 00000000..b7381fa1 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.inventory; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockExpEvent; + +/** + * This event is called when a player takes items out of the furnace + */ +public class FurnaceExtractEvent extends BlockExpEvent { + private final Player player; + private final Material itemType; + private final int itemAmount; + + public FurnaceExtractEvent(Player player, Block block, Material itemType, int itemAmount, int exp) { + super(block, exp); + this.player = player; + this.itemType = itemType; + this.itemAmount = itemAmount; + } + + /** + * Get the player that triggered the event + * + * @return the relevant player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the Material of the item being retrieved + * + * @return the material of the item + */ + public Material getItemType() { + return itemType; + } + + /** + * Get the item count being retrieved + * + * @return the amount of the item + */ + public int getItemAmount() { + return itemAmount; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java b/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java new file mode 100644 index 00000000..5ad31796 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java @@ -0,0 +1,68 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an ItemStack is successfully smelted in a furnace. + */ +public class FurnaceSmeltEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack source; + private ItemStack result; + private boolean cancelled; + + public FurnaceSmeltEvent(final Block furnace, final ItemStack source, final ItemStack result) { + super(furnace); + this.source = source; + this.result = result; + this.cancelled = false; + } + + /** + * Gets the smelted ItemStack for this event + * + * @return smelting source ItemStack + */ + public ItemStack getSource() { + return source; + } + + /** + * Gets the resultant ItemStack for this event + * + * @return smelting result ItemStack + */ + public ItemStack getResult() { + return result; + } + + /** + * Sets the resultant ItemStack for this event + * + * @param result new result ItemStack + */ + public void setResult(ItemStack result) { + this.result = result; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryAction.java b/src/main/java/org/bukkit/event/inventory/InventoryAction.java new file mode 100644 index 00000000..a7bc694b --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryAction.java @@ -0,0 +1,91 @@ +package org.bukkit.event.inventory; + +/** + * An estimation of what the result will be. + */ +public enum InventoryAction { + + /** + * Nothing will happen from the click. + *

    + * There may be cases where nothing will happen and this is value is not + * provided, but it is guaranteed that this value is accurate when given. + */ + NOTHING, + /** + * All of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ALL, + /** + * Some of the items on the clicked slot are moved to the cursor. + */ + PICKUP_SOME, + /** + * Half of the items on the clicked slot are moved to the cursor. + */ + PICKUP_HALF, + /** + * One of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ONE, + /** + * All of the items on the cursor are moved to the clicked slot. + */ + PLACE_ALL, + /** + * Some of the items from the cursor are moved to the clicked slot + * (usually up to the max stack size). + */ + PLACE_SOME, + /** + * A single item from the cursor is moved to the clicked slot. + */ + PLACE_ONE, + /** + * The clicked item and the cursor are exchanged. + */ + SWAP_WITH_CURSOR, + /** + * The entire cursor item is dropped. + */ + DROP_ALL_CURSOR, + /** + * One item is dropped from the cursor. + */ + DROP_ONE_CURSOR, + /** + * The entire clicked slot is dropped. + */ + DROP_ALL_SLOT, + /** + * One item is dropped from the clicked slot. + */ + DROP_ONE_SLOT, + /** + * The item is moved to the opposite inventory if a space is found. + */ + MOVE_TO_OTHER_INVENTORY, + /** + * The clicked item is moved to the hotbar, and the item currently there + * is re-added to the player's inventory. + */ + HOTBAR_MOVE_AND_READD, + /** + * The clicked slot and the picked hotbar slot are swapped. + */ + HOTBAR_SWAP, + /** + * A max-size stack of the clicked item is put on the cursor. + */ + CLONE_STACK, + /** + * The inventory is searched for the same material, and they are put on + * the cursor up to {@link org.bukkit.Material#getMaxStackSize()}. + */ + COLLECT_TO_CURSOR, + /** + * An unrecognized ClickType. + */ + UNKNOWN, + ; +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java new file mode 100644 index 00000000..60feaf3b --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java @@ -0,0 +1,240 @@ +package org.bukkit.event.inventory; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.plugin.Plugin; + +/** + * This event is called when a player clicks a slot in an inventory. + *

    + * Because InventoryClickEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

    + * The following should never be invoked by an EventHandler for + * InventoryClickEvent using the HumanEntity or InventoryView associated with + * this event: + *

      + *
    • {@link HumanEntity#closeInventory()} + *
    • {@link HumanEntity#openInventory(Inventory)} + *
    • {@link HumanEntity#openWorkbench(Location, boolean)} + *
    • {@link HumanEntity#openEnchanting(Location, boolean)} + *
    • {@link InventoryView#close()} + *
    + * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

    + * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

    + * Modifications to slots that are modified by the results of this + * InventoryClickEvent can be overwritten. To change these slots, this event + * should be cancelled and all desired changes to the inventory applied. + * Alternatively, scheduling a task using {@link BukkitScheduler#runTask( + * Plugin, Runnable)}, which would execute the task on the next tick, would + * work as well. + */ +public class InventoryClickEvent extends InventoryInteractEvent { + private static final HandlerList handlers = new HandlerList(); + private final ClickType click; + private final InventoryAction action; + private final Inventory clickedInventory; + private SlotType slot_type; + private int whichSlot; + private int rawSlot; + private ItemStack current = null; + private int hotbarKey = -1; + + public InventoryClickEvent(InventoryView view, SlotType type, int slot, ClickType click, InventoryAction action) { + super(view); + this.slot_type = type; + this.rawSlot = slot; + if (slot < 0) { + this.clickedInventory = null; + } else if (view.getTopInventory() != null && slot < view.getTopInventory().getSize()) { + this.clickedInventory = view.getTopInventory(); + } else { + this.clickedInventory = view.getBottomInventory(); + } + this.whichSlot = view.convertSlot(slot); + this.click = click; + this.action = action; + } + + public InventoryClickEvent(InventoryView view, SlotType type, int slot, ClickType click, InventoryAction action, int key) { + this(view, type, slot, click, action); + this.hotbarKey = key; + } + + /** + * Gets the inventory that was clicked, or null if outside of window + * @return The clicked inventory + */ + public Inventory getClickedInventory() { + return clickedInventory; + } + + /** + * Gets the type of slot that was clicked. + * + * @return the slot type + */ + public SlotType getSlotType() { + return slot_type; + } + + /** + * Gets the current ItemStack on the cursor. + * + * @return the cursor ItemStack + */ + public ItemStack getCursor() { + return getView().getCursor(); + } + + /** + * Gets the ItemStack currently in the clicked slot. + * + * @return the item in the clicked + */ + public ItemStack getCurrentItem() { + if (slot_type == SlotType.OUTSIDE) { + return current; + } + return getView().getItem(rawSlot); + } + + /** + * Gets whether or not the ClickType for this event represents a right + * click. + * + * @return true if the ClickType uses the right mouse button. + * @see ClickType#isRightClick() + */ + public boolean isRightClick() { + return click.isRightClick(); + } + + /** + * Gets whether or not the ClickType for this event represents a left + * click. + * + * @return true if the ClickType uses the left mouse button. + * @see ClickType#isLeftClick() + */ + public boolean isLeftClick() { + return click.isLeftClick(); + } + + /** + * Gets whether the ClickType for this event indicates that the key was + * pressed down when the click was made. + * + * @return true if the ClickType uses Shift or Ctrl. + * @see ClickType#isShiftClick() + */ + public boolean isShiftClick() { + return click.isShiftClick(); + } + + /** + * Sets the item on the cursor. + * + * @param stack the new cursor item + * @deprecated This changes the ItemStack in their hand before any + * calculations are applied to the Inventory, which has a tendency to + * create inconsistencies between the Player and the server, and to + * make unexpected changes in the behavior of the clicked Inventory. + */ + @Deprecated + public void setCursor(ItemStack stack) { + getView().setCursor(stack); + } + + /** + * Sets the ItemStack currently in the clicked slot. + * + * @param stack the item to be placed in the current slot + */ + public void setCurrentItem(ItemStack stack) { + if (slot_type == SlotType.OUTSIDE) { + current = stack; + } else { + getView().setItem(rawSlot, stack); + } + } + + /** + * The slot number that was clicked, ready for passing to + * {@link Inventory#getItem(int)}. Note that there may be two slots with + * the same slot number, since a view links two different inventories. + * + * @return The slot number. + */ + public int getSlot() { + return whichSlot; + } + + /** + * The raw slot number clicked, ready for passing to {@link InventoryView + * #getItem(int)} This slot number is unique for the view. + * + * @return the slot number + */ + public int getRawSlot() { + return rawSlot; + } + + /** + * If the ClickType is NUMBER_KEY, this method will return the index of + * the pressed key (0-8). + * + * @return the number on the key minus 1 (range 0-8); or -1 if not + * a NUMBER_KEY action + */ + public int getHotbarButton() { + return hotbarKey; + } + + /** + * Gets the InventoryAction that triggered this event. + *

    + * This action cannot be changed, and represents what the normal outcome + * of the event will be. To change the behavior of this + * InventoryClickEvent, changes must be manually applied. + * + * @return the InventoryAction that triggered this event. + */ + public InventoryAction getAction() { + return action; + } + + /** + * Gets the ClickType for this event. + *

    + * This is insulated against changes to the inventory by other plugins. + * + * @return the type of inventory click + */ + public ClickType getClick() { + return click; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java new file mode 100644 index 00000000..19889b27 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java @@ -0,0 +1,35 @@ + +package org.bukkit.event.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.InventoryView; + +/** + * Represents a player related inventory event + */ +public class InventoryCloseEvent extends InventoryEvent { + private static final HandlerList handlers = new HandlerList(); + + public InventoryCloseEvent(InventoryView transaction) { + super(transaction); + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + public final HumanEntity getPlayer() { + return transaction.getPlayer(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java new file mode 100644 index 00000000..da7dffc0 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * This event is called when a player in creative mode puts down or picks up + * an item in their inventory / hotbar and when they drop items from their + * Inventory while in creative mode. + */ +public class InventoryCreativeEvent extends InventoryClickEvent { + private ItemStack item; + + public InventoryCreativeEvent(InventoryView what, SlotType type, int slot, ItemStack newItem) { + super(what, type, slot, ClickType.CREATIVE, InventoryAction.PLACE_ALL); + this.item = newItem; + } + + public ItemStack getCursor() { + return item; + } + + public void setCursor(ItemStack item) { + this.item = item; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java new file mode 100644 index 00000000..9e52f4a1 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java @@ -0,0 +1,164 @@ +package org.bukkit.event.inventory; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; + +import com.google.common.collect.ImmutableSet; + +/** + * This event is called when the player drags an item in their cursor across + * the inventory. The ItemStack is distributed across the slots the + * HumanEntity dragged over. The method of distribution is described by the + * DragType returned by {@link #getType()}. + *

    + * Canceling this event will result in none of the changes described in + * {@link #getNewItems()} being applied to the Inventory. + *

    + * Because InventoryDragEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

    + * The following should never be invoked by an EventHandler for + * InventoryDragEvent using the HumanEntity or InventoryView associated with + * this event. + *

      + *
    • {@link HumanEntity#closeInventory()} + *
    • {@link HumanEntity#openInventory(Inventory)} + *
    • {@link HumanEntity#openWorkbench(Location, boolean)} + *
    • {@link HumanEntity#openEnchanting(Location, boolean)} + *
    • {@link InventoryView#close()} + *
    + * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

    + * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

    + * Any modifications to slots that are modified by the results of this + * InventoryDragEvent will be overwritten. To change these slots, this event + * should be cancelled and the changes applied. Alternatively, scheduling a + * task using {@link BukkitScheduler#runTask(Plugin, Runnable)}, which would + * execute the task on the next tick, would work as well. + */ +public class InventoryDragEvent extends InventoryInteractEvent { + private static final HandlerList handlers = new HandlerList(); + private final DragType type; + private final Map addedItems; + private final Set containerSlots; + private final ItemStack oldCursor; + private ItemStack newCursor; + + public InventoryDragEvent(InventoryView what, ItemStack newCursor, ItemStack oldCursor, boolean right, Map slots) { + super(what); + + Validate.notNull(oldCursor); + Validate.notNull(slots); + + type = right ? DragType.SINGLE : DragType.EVEN; + this.newCursor = newCursor; + this.oldCursor = oldCursor; + this.addedItems = slots; + ImmutableSet.Builder b = ImmutableSet.builder(); + for (Integer slot : slots.keySet()) { + b.add(what.convertSlot(slot)); + } + this.containerSlots = b.build(); + } + + /** + * Gets all items to be added to the inventory in this drag. + * + * @return map from raw slot id to new ItemStack + */ + public Map getNewItems() { + return Collections.unmodifiableMap(addedItems); + } + + /** + * Gets the raw slot ids to be changed in this drag. + * + * @return list of raw slot ids, suitable for getView().getItem(int) + */ + public Set getRawSlots() { + return addedItems.keySet(); + } + + /** + * Gets the slots to be changed in this drag. + * + * @return list of converted slot ids, suitable for {@link + * Inventory#getItem(int)}. + */ + public Set getInventorySlots() { + return containerSlots; + } + + /** + * Gets the result cursor after the drag is done. The returned value is + * mutable. + * + * @return the result cursor + */ + public ItemStack getCursor() { + return newCursor; + } + + /** + * Sets the result cursor after the drag is done. + *

    + * Changing this item stack changes the cursor item. Note that changing + * the affected "dragged" slots does not change this ItemStack, nor does + * changing this ItemStack affect the "dragged" slots. + * + * @param newCursor the new cursor ItemStack + */ + public void setCursor(ItemStack newCursor) { + this.newCursor = newCursor; + } + + /** + * Gets an ItemStack representing the cursor prior to any modifications + * as a result of this drag. + * + * @return the original cursor + */ + public ItemStack getOldCursor() { + return oldCursor.clone(); + } + + /** + * Gets the DragType that describes the behavior of ItemStacks placed + * after this InventoryDragEvent. + *

    + * The ItemStacks and the raw slots that they're being applied to can be + * found using {@link #getNewItems()}. + * + * @return the DragType of this InventoryDragEvent + */ + public DragType getType() { + return type; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryEvent.java new file mode 100644 index 00000000..973c392e --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryEvent.java @@ -0,0 +1,59 @@ + +package org.bukkit.event.inventory; + +import java.util.List; + +import org.bukkit.event.HandlerList; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; + +/** + * Represents a player related inventory event + */ +public class InventoryEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + protected InventoryView transaction; + + public InventoryEvent(InventoryView transaction) { + this.transaction = transaction; + } + + /** + * Gets the primary Inventory involved in this transaction + * + * @return The upper inventory. + */ + public Inventory getInventory() { + return transaction.getTopInventory(); + } + + /** + * Gets the list of players viewing the primary (upper) inventory involved + * in this event + * + * @return A list of people viewing. + */ + public List getViewers() { + return transaction.getTopInventory().getViewers(); + } + + /** + * Gets the view object itself + * + * @return InventoryView + */ + public InventoryView getView() { + return transaction; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java new file mode 100644 index 00000000..2d575056 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java @@ -0,0 +1,77 @@ +package org.bukkit.event.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event.Result; +import org.bukkit.inventory.InventoryView; + +/** + * An abstract base class for events that describe an interaction between a + * HumanEntity and the contents of an Inventory. + */ +public abstract class InventoryInteractEvent extends InventoryEvent implements Cancellable { + private Result result = Result.DEFAULT; + + public InventoryInteractEvent(InventoryView transaction) { + super(transaction); + } + + /** + * Gets the player who performed the click. + * + * @return The clicking player. + */ + public HumanEntity getWhoClicked() { + return getView().getPlayer(); + } + + /** + * Sets the result of this event. This will change whether or not this + * event is considered cancelled. + * + * @see #isCancelled() + * @param newResult the new {@link Result} for this event + */ + public void setResult(Result newResult) { + result = newResult; + } + + /** + * Gets the {@link Result} of this event. The Result describes the + * behavior that will be applied to the inventory in relation to this + * event. + * + * @return the Result of this event. + */ + public Result getResult() { + return result; + } + + /** + * Gets whether or not this event is cancelled. This is based off of the + * Result value returned by {@link #getResult()}. Result.ALLOW and + * Result.DEFAULT will result in a returned value of false, but + * Result.DENY will result in a returned value of true. + *

    + * {@inheritDoc} + * + * @return whether the event is cancelled + */ + public boolean isCancelled() { + return getResult() == Result.DENY; + } + + /** + * Proxy method to {@link #setResult(Event.Result)} for the Cancellable + * interface. {@link #setResult(Event.Result)} is preferred, as it allows + * you to specify the Result beyond Result.DENY and Result.ALLOW. + *

    + * {@inheritDoc} + * + * @param toCancel result becomes DENY if true, ALLOW if false + */ + public void setCancelled(boolean toCancel) { + setResult(toCancel ? Result.DENY : Result.ALLOW); + } + +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java new file mode 100644 index 00000000..8afd0288 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java @@ -0,0 +1,108 @@ +package org.bukkit.event.inventory; + +import org.apache.commons.lang3.Validate; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +/** + * Called when some entity or block (e.g. hopper) tries to move items directly + * from one inventory to another. + *

    + * When this event is called, the initiator may already have removed the item + * from the source inventory and is ready to move it into the destination + * inventory. + *

    + * If this event is cancelled, the items will be returned to the source + * inventory, if needed. + *

    + * If this event is not cancelled, the initiator will try to put the ItemStack + * into the destination inventory. If this is not possible and the ItemStack + * has not been modified, the source inventory slot will be restored to its + * former state. Otherwise any additional items will be discarded. + */ +public class InventoryMoveItemEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Inventory sourceInventory; + private final Inventory destinationInventory; + private ItemStack itemStack; + private final boolean didSourceInitiate; + + public InventoryMoveItemEvent(final Inventory sourceInventory, final ItemStack itemStack, final Inventory destinationInventory, final boolean didSourceInitiate) { + Validate.notNull(itemStack, "ItemStack cannot be null"); + this.sourceInventory = sourceInventory; + this.itemStack = itemStack; + this.destinationInventory = destinationInventory; + this.didSourceInitiate = didSourceInitiate; + } + + /** + * Gets the Inventory that the ItemStack is being taken from + * + * @return Inventory that the ItemStack is being taken from + */ + public Inventory getSource() { + return sourceInventory; + } + + /** + * Gets the ItemStack being moved; if modified, the original item will not + * be removed from the source inventory. + * + * @return ItemStack + */ + public ItemStack getItem() { + return itemStack.clone(); + } + + /** + * Sets the ItemStack being moved; if this is different from the original + * ItemStack, the original item will not be removed from the source + * inventory. + * + * @param itemStack The ItemStack + */ + public void setItem(ItemStack itemStack) { + Validate.notNull(itemStack, "ItemStack cannot be null. Cancel the event if you want nothing to be transferred."); + this.itemStack = itemStack.clone(); + } + + /** + * Gets the Inventory that the ItemStack is being put into + * + * @return Inventory that the ItemStack is being put into + */ + public Inventory getDestination() { + return destinationInventory; + } + + /** + * Gets the Inventory that initiated the transfer. This will always be + * either the destination or source Inventory. + * + * @return Inventory that initiated the transfer + */ + public Inventory getInitiator() { + return didSourceInitiate ? sourceInventory : destinationInventory; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java new file mode 100644 index 00000000..c3570aae --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java @@ -0,0 +1,63 @@ +package org.bukkit.event.inventory; + +import org.bukkit.inventory.InventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Represents a player related inventory event + */ +public class InventoryOpenEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public InventoryOpenEvent(InventoryView transaction) { + super(transaction); + this.cancelled = false; + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + public final HumanEntity getPlayer() { + return transaction.getPlayer(); + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + *

    + * If an inventory open event is cancelled, the inventory screen will not + * show. + * + * @return true if this event is cancelled + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + *

    + * If an inventory open event is cancelled, the inventory screen will not + * show. + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java new file mode 100644 index 00000000..af6ad5b7 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java @@ -0,0 +1,58 @@ +package org.bukkit.event.inventory; + +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; + +/** + * Called when a hopper or hopper minecart picks up a dropped item. + */ +public class InventoryPickupItemEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Inventory inventory; + private final Item item; + + public InventoryPickupItemEvent(final Inventory inventory, final Item item) { + super(); + this.inventory = inventory; + this.item = item; + } + + /** + * Gets the Inventory that picked up the item + * + * @return Inventory + */ + public Inventory getInventory() { + return inventory; + } + + /** + * Gets the Item entity that was picked up + * + * @return Item + */ + public Item getItem() { + return item; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java new file mode 100644 index 00000000..f6446392 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java @@ -0,0 +1,129 @@ +package org.bukkit.event.inventory; + +public enum InventoryType { + + /** + * A chest inventory, with 0, 9, 18, 27, 36, 45, or 54 slots of type + * CONTAINER. + */ + CHEST(27,"Chest"), + /** + * A dispenser inventory, with 9 slots of type CONTAINER. + */ + DISPENSER(9,"Dispenser"), + /** + * A dropper inventory, with 9 slots of type CONTAINER. + */ + DROPPER(9, "Dropper"), + /** + * A furnace inventory, with a RESULT slot, a CRAFTING slot, and a FUEL + * slot. + */ + FURNACE(3,"Furnace"), + /** + * A workbench inventory, with 9 CRAFTING slots and a RESULT slot. + */ + WORKBENCH(10,"Crafting"), + /** + * A player's crafting inventory, with 4 CRAFTING slots and a RESULT slot. + * Also implies that the 4 ARMOR slots are accessible. + */ + CRAFTING(5,"Crafting"), + /** + * An enchantment table inventory, with two CRAFTING slots and three + * enchanting buttons. + */ + ENCHANTING(2,"Enchanting"), + /** + * A brewing stand inventory, with one FUEL slot and three CRAFTING slots. + */ + BREWING(5,"Brewing"), + /** + * A player's inventory, with 9 QUICKBAR slots, 27 CONTAINER slots, 4 ARMOR + * slots and 1 offhand slot. The ARMOR and offhand slots may not be visible + * to the player, though. + */ + PLAYER(41,"Player"), + /** + * The creative mode inventory, with only 9 QUICKBAR slots and nothing + * else. (The actual creative interface with the items is client-side and + * cannot be altered by the server.) + */ + CREATIVE(9,"Creative"), + /** + * The merchant inventory, with 2 TRADE-IN slots, and 1 RESULT slot. + */ + MERCHANT(3,"Villager"), + /** + * The ender chest inventory, with 27 slots. + */ + ENDER_CHEST(27,"Ender Chest"), + /** + * An anvil inventory, with 2 CRAFTING slots and 1 RESULT slot + */ + ANVIL(3, "Repairing"), + /** + * A beacon inventory, with 1 CRAFTING slot + */ + BEACON(1, "container.beacon"), + /** + * A hopper inventory, with 5 slots of type CONTAINER. + */ + HOPPER(5, "Item Hopper"), + /** + * A shulker box inventory, with 27 slots of type CONTAINER. + */ + SHULKER_BOX(27, "Shulker Box"), + ; + + private final int size; + private final String title; + + private InventoryType(int defaultSize, String defaultTitle) { + size = defaultSize; + title = defaultTitle; + } + + public int getDefaultSize() { + return size; + } + + public String getDefaultTitle() { + return title; + } + + public enum SlotType { + /** + * A result slot in a furnace or crafting inventory. + */ + RESULT, + /** + * A slot in the crafting matrix, or the input slot in a furnace + * inventory, the potion slot in the brewing stand, or the enchanting + * slot. + */ + CRAFTING, + /** + * An armour slot in the player's inventory. + */ + ARMOR, + /** + * A regular slot in the container or the player's inventory; anything + * not covered by the other enum values. + */ + CONTAINER, + /** + * A slot in the bottom row or quickbar. + */ + QUICKBAR, + /** + * A pseudo-slot representing the area outside the inventory window. + */ + OUTSIDE, + /** + * The fuel slot in a furnace inventory, or the ingredient slot in a + * brewing stand inventory. + */ + FUEL; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java b/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java new file mode 100644 index 00000000..ce496767 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java @@ -0,0 +1,47 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * Called when an item is put in a slot for repair by an anvil. + */ +public class PrepareAnvilEvent extends InventoryEvent { + + private static final HandlerList handlers = new HandlerList(); + private ItemStack result; + + public PrepareAnvilEvent(InventoryView inventory, ItemStack result) { + super(inventory); + this.result = result; + } + + @Override + public AnvilInventory getInventory() { + return (AnvilInventory) super.getInventory(); + } + + /** + * Get result item, may be null. + * + * @return result item + */ + public ItemStack getResult() { + return result; + } + + public void setResult(ItemStack result) { + this.result = result; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java new file mode 100644 index 00000000..57311904 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Recipe; + +public class PrepareItemCraftEvent extends InventoryEvent { + private static final HandlerList handlers = new HandlerList(); + private boolean repair; + private CraftingInventory matrix; + + public PrepareItemCraftEvent(CraftingInventory what, InventoryView view, boolean isRepair) { + super(view); + this.matrix = what; + this.repair = isRepair; + } + + /** + * Get the recipe that has been formed. If this event was triggered by a + * tool repair, this will be a temporary shapeless recipe representing the + * repair. + * + * @return The recipe being crafted. + */ + public Recipe getRecipe() { + return matrix.getRecipe(); + } + + /** + * @return The crafting inventory on which the recipe was formed. + */ + @Override + public CraftingInventory getInventory() { + return matrix; + } + + /** + * Check if this event was triggered by a tool repair operation rather + * than a crafting recipe. + * + * @return True if this is a repair. + */ + public boolean isRepair() { + return repair; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java b/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java new file mode 100644 index 00000000..680a632e --- /dev/null +++ b/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java @@ -0,0 +1,140 @@ +package org.bukkit.event.player; + +import java.util.IllegalFormatException; +import java.util.Set; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * This event will sometimes fire synchronously, depending on how it was + * triggered. + *

    + * The constructor provides a boolean to indicate if the event was fired + * synchronously or asynchronously. When asynchronous, this event can be + * called from any thread, sans the main thread, and has limited access to the + * API. + *

    + * If a player is the direct cause of this event by an incoming packet, this + * event will be asynchronous. If a plugin triggers this event by compelling a + * player to chat, this event will be synchronous. + *

    + * Care should be taken to check {@link #isAsynchronous()} and treat the event + * appropriately. + */ +public class AsyncPlayerChatEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private String format = "<%1$s> %2$s"; + private final Set recipients; + + /** + * + * @param async This changes the event to a synchronous state. + * @param who the chat sender + * @param message the message sent + * @param players the players to receive the message. This may be a lazy + * or unmodifiable collection. + */ + public AsyncPlayerChatEvent(final boolean async, final Player who, final String message, final Set players) { + super(who, async); + this.message = message; + recipients = players; + } + + /** + * Gets the message that the player is attempting to send. This message + * will be used with {@link #getFormat()}. + * + * @return Message the player is attempting to send + */ + public String getMessage() { + return message; + } + + /** + * Sets the message that the player will send. This message will be used + * with {@link #getFormat()}. + * + * @param message New message that the player will send + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Gets the format to use to display this chat message. + *

    + * When this event finishes execution, the first format parameter is the + * {@link Player#getDisplayName()} and the second parameter is {@link + * #getMessage()} + * + * @return {@link String#format(String, Object...)} compatible format + * string + */ + public String getFormat() { + return format; + } + + /** + * Sets the format to use to display this chat message. + *

    + * When this event finishes execution, the first format parameter is the + * {@link Player#getDisplayName()} and the second parameter is {@link + * #getMessage()} + * + * @param format {@link String#format(String, Object...)} compatible + * format string + * @throws IllegalFormatException if the underlying API throws the + * exception + * @throws NullPointerException if format is null + * @see String#format(String, Object...) + */ + public void setFormat(final String format) throws IllegalFormatException, NullPointerException { + // Oh for a better way to do this! + try { + String.format(format, player, message); + } catch (RuntimeException ex) { + ex.fillInStackTrace(); + throw ex; + } + + this.format = format; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

    + * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. + *

    + * Listeners should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @return All Players who will see this chat message + */ + public Set getRecipients() { + return recipients; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java new file mode 100644 index 00000000..cf34c69b --- /dev/null +++ b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java @@ -0,0 +1,227 @@ +package org.bukkit.event.player; + +import com.destroystokyo.paper.profile.PlayerProfile; +import java.net.InetAddress; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Stores details for players attempting to log in. + *

    + * This event is asynchronous, and not run using main thread. + */ +public class AsyncPlayerPreLoginEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private Result result; + private String message; + private final String name; + private final InetAddress ipAddress; + private final UUID uniqueId; + + @Deprecated + public AsyncPlayerPreLoginEvent(final String name, final InetAddress ipAddress) { + this(name, ipAddress, null); + } + + public AsyncPlayerPreLoginEvent(final String name, final InetAddress ipAddress, final UUID uniqueId) { + // Paper start + this(name, ipAddress, uniqueId, Bukkit.createProfile(uniqueId, name)); + } + private PlayerProfile profile; + + /** + * Gets the PlayerProfile of the player logging in + * @return The Profile + */ + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Changes the PlayerProfile the player will login as + * @param profile The profile to use + */ + public void setPlayerProfile(PlayerProfile profile) { + this.profile = profile; + } + + public AsyncPlayerPreLoginEvent(final String name, final InetAddress ipAddress, final UUID uniqueId, PlayerProfile profile) { + super(true); + this.profile = profile; + // Paper end + this.result = Result.ALLOWED; + this.message = ""; + this.name = name; + this.ipAddress = ipAddress; + this.uniqueId = uniqueId; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + public Result getLoginResult() { + return result; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #getLoginResult() + */ + @Deprecated + public PlayerPreLoginEvent.Result getResult() { + return result == null ? null : result.old(); + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setLoginResult(final Result result) { + this.result = result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #setLoginResult(Result) + */ + @Deprecated + public void setResult(final PlayerPreLoginEvent.Result result) { + this.result = result == null ? null : Result.valueOf(result.name()); + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(final String message) { + this.message = message; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(final Result result, final String message) { + this.result = result; + this.message = message; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #disallow(Result, String) + */ + @Deprecated + public void disallow(final PlayerPreLoginEvent.Result result, final String message) { + this.result = result == null ? null : Result.valueOf(result.name()); + this.message = message; + } + + /** + * Gets the player's name. + * + * @return the player's name + */ + public String getName() { + return name; + } + + /** + * Gets the player IP address. + * + * @return The IP address + */ + public InetAddress getAddress() { + return ipAddress; + } + + /** + * Gets the player's unique ID. + * + * @return The unique ID + */ + public UUID getUniqueId() { + return uniqueId; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER; + + @Deprecated + private PlayerPreLoginEvent.Result old() { + return PlayerPreLoginEvent.Result.valueOf(name()); + } + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java b/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java new file mode 100644 index 00000000..a57a93d7 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.Achievement; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player earns an achievement. + * @deprecated future versions of Minecraft do not have achievements + */ +@Deprecated +public class PlayerAchievementAwardedEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Achievement achievement; + private boolean isCancelled = false; + + public PlayerAchievementAwardedEvent(Player player, Achievement achievement) { + super(player); + this.achievement = achievement; + } + + /** + * Gets the Achievement being awarded. + * + * @return the achievement being awarded + */ + public Achievement getAchievement() { + return achievement; + } + + public boolean isCancelled() { + return isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java b/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java new file mode 100644 index 00000000..d925a18f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.player; + +import org.bukkit.advancement.Advancement; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player has completed all criteria in an advancement. + */ +public class PlayerAdvancementDoneEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final Advancement advancement; + + public PlayerAdvancementDoneEvent(Player who, Advancement advancement) { + super(who); + this.advancement = advancement; + } + + /** + * Get the advancement which has been completed. + * + * @return completed advancement + */ + public Advancement getAdvancement() { + return advancement; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java b/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java new file mode 100644 index 00000000..cabe77dd --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java @@ -0,0 +1,52 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Represents a player animation event + */ +public class PlayerAnimationEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final PlayerAnimationType animationType; + private boolean isCancelled = false; + + /** + * Construct a new PlayerAnimation event + * + * @param player The player instance + */ + public PlayerAnimationEvent(final Player player) { + super(player); + + // Only supported animation type for now: + animationType = PlayerAnimationType.ARM_SWING; + } + + /** + * Get the type of this animation event + * + * @return the animation type + */ + public PlayerAnimationType getAnimationType() { + return animationType; + } + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerAnimationType.java b/src/main/java/org/bukkit/event/player/PlayerAnimationType.java new file mode 100644 index 00000000..ea4bf26f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerAnimationType.java @@ -0,0 +1,8 @@ +package org.bukkit.event.player; + +/** + * Different types of player animations + */ +public enum PlayerAnimationType { + ARM_SWING +} diff --git a/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java b/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java new file mode 100644 index 00000000..577b2844 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.player; + +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.entity.Player; +import org.bukkit.entity.ArmorStand; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player interacts with an armor stand and will either swap, retrieve or place an item. + */ +public class PlayerArmorStandManipulateEvent extends PlayerInteractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + + private final ItemStack playerItem; + private final ItemStack armorStandItem; + private final EquipmentSlot slot; + + public PlayerArmorStandManipulateEvent(final Player who, final ArmorStand clickedEntity, final ItemStack playerItem, final ItemStack armorStandItem, final EquipmentSlot slot) { + super(who, clickedEntity); + this.playerItem = playerItem; + this.armorStandItem = armorStandItem; + this.slot = slot; + } + + /** + * Returns the item held by the player. If this Item is null and the armor stand Item is also null, + * there will be no transaction between the player and the armor stand. + * If the Player's item is null, but the armor stand item is not then the player will obtain the armor stand item. + * In the case that the Player's item is not null, but the armor stand item is null, the players item will be placed on the armor stand. + * If both items are not null, the items will be swapped. + * In the case that the event is cancelled the original items will remain the same. + * @return the item held by the player. + */ + public ItemStack getPlayerItem() { + return this.playerItem; + } + + /** + * Returns the item held by the armor stand. + * If this Item is null and the player's Item is also null, there will be no transaction between the player and the armor stand. + * If the Player's item is null, but the armor stand item is not then the player will obtain the armor stand item. + * In the case that the Player's item is not null, but the armor stand item is null, the players item will be placed on the armor stand. + * If both items are not null, the items will be swapped. + * In the case that the event is cancelled the original items will remain the same. + * @return the item held by the armor stand. + */ + public ItemStack getArmorStandItem() { + return this.armorStandItem; + } + + /** + * Returns the raw item slot of the armor stand in this event. + * + * @return the index of the item obtained or placed of the armor stand. + */ + public EquipmentSlot getSlot() { + return this.slot; + } + + @Override + public ArmorStand getRightClicked() { + return (ArmorStand) this.clickedEntity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java new file mode 100644 index 00000000..09f1a669 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.player; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * This event is fired when the player is almost about to enter the bed. + */ +public class PlayerBedEnterEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final Block bed; + + public PlayerBedEnterEvent(final Player who, final Block bed) { + super(who); + this.bed = bed; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the bed block involved in this event. + * + * @return the bed block involved in this event + */ + public Block getBed() { + return bed; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java b/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java new file mode 100644 index 00000000..628ab0b0 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.player; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * This event is fired when the player is leaving a bed. + */ +public class PlayerBedLeaveEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block bed; + + public PlayerBedLeaveEvent(final Player who, final Block bed) { + super(who); + this.bed = bed; + } + + /** + * Returns the bed block involved in this event. + * + * @return the bed block involved in this event + */ + public Block getBed() { + return bed; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java b/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java new file mode 100644 index 00000000..8fb121a9 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java @@ -0,0 +1,28 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player empties a bucket + */ +public class PlayerBucketEmptyEvent extends PlayerBucketEvent { + private static final HandlerList handlers = new HandlerList(); + + public PlayerBucketEmptyEvent(final Player who, final Block blockClicked, final BlockFace blockFace, final Material bucket, final ItemStack itemInHand) { + super(who, blockClicked, blockFace, bucket, itemInHand); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java b/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java new file mode 100644 index 00000000..56584687 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java @@ -0,0 +1,80 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player interacts with a Bucket + */ +public abstract class PlayerBucketEvent extends PlayerEvent implements Cancellable { + private ItemStack itemStack; + private boolean cancelled = false; + private final Block blockClicked; + private final BlockFace blockFace; + private final Material bucket; + + public PlayerBucketEvent(final Player who, final Block blockClicked, final BlockFace blockFace, final Material bucket, final ItemStack itemInHand) { + super(who); + this.blockClicked = blockClicked; + this.blockFace = blockFace; + this.itemStack = itemInHand; + this.bucket = bucket; + } + + /** + * Returns the bucket used in this event + * + * @return the used bucket + */ + public Material getBucket() { + return bucket; + } + + /** + * Get the resulting item in hand after the bucket event + * + * @return Itemstack hold in hand after the event. + */ + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Set the item in hand after the event + * + * @param itemStack the new held itemstack after the bucket event. + */ + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + } + + /** + * Return the block clicked + * + * @return the clicked block + */ + public Block getBlockClicked() { + return blockClicked; + } + + /** + * Get the face on the clicked block + * + * @return the clicked face + */ + public BlockFace getBlockFace() { + return blockFace; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java b/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java new file mode 100644 index 00000000..94e042a3 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java @@ -0,0 +1,28 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player fills a bucket + */ +public class PlayerBucketFillEvent extends PlayerBucketEvent { + private static final HandlerList handlers = new HandlerList(); + + public PlayerBucketFillEvent(final Player who, final Block blockClicked, final BlockFace blockFace, final Material bucket, final ItemStack itemInHand) { + super(who, blockClicked, blockFace, bucket, itemInHand); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java b/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java new file mode 100644 index 00000000..2a154462 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java @@ -0,0 +1,39 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MainHand; + +/** + * Called when a player changes their main hand in the client settings. + */ +public class PlayerChangedMainHandEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final MainHand mainHand; + + public PlayerChangedMainHandEvent(Player who, MainHand mainHand) { + super(who); + this.mainHand = mainHand; + } + + /** + * Gets the new main hand of the player. The old hand is still momentarily + * available via {@link Player#getMainHand()}. + * + * @return the new {@link MainHand} of the player + */ + public MainHand getMainHand() { + return mainHand; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java b/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java new file mode 100644 index 00000000..76c9c209 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.player; + +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player switches to another world. + */ +public class PlayerChangedWorldEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final World from; + + public PlayerChangedWorldEvent(final Player player, final World from) { + super(player); + this.from = from; + } + + /** + * Gets the world the player is switching from. + * + * @return player's previous world + */ + public World getFrom() { + return from; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java new file mode 100644 index 00000000..054efbc3 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java @@ -0,0 +1,31 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * This event is called after a player registers or unregisters a new plugin + * channel. + */ +public abstract class PlayerChannelEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String channel; + + public PlayerChannelEvent(final Player player, final String channel) { + super(player); + this.channel = channel; + } + + public final String getChannel() { + return channel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerChatEvent.java b/src/main/java/org/bukkit/event/player/PlayerChatEvent.java new file mode 100644 index 00000000..f3631448 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChatEvent.java @@ -0,0 +1,125 @@ +package org.bukkit.event.player; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Warning; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Holds information for player chat and commands + * + * @deprecated This event will fire from the main thread and allows the use of + * all of the Bukkit API, unlike the {@link AsyncPlayerChatEvent}. + *

    + * Listening to this event forces chat to wait for the main thread which + * causes delays for chat. {@link AsyncPlayerChatEvent} is the encouraged + * alternative for thread safe implementations. + */ +@Deprecated +@Warning(reason="Listening to this event forces chat to wait for the main thread, delaying chat messages.") +public class PlayerChatEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private String format; + private final Set recipients; + + public PlayerChatEvent(final Player player, final String message) { + super(player); + this.message = message; + this.format = "<%1$s> %2$s"; + this.recipients = new HashSet(player.getServer().getOnlinePlayers()); + } + + public PlayerChatEvent(final Player player, final String message, final String format, final Set recipients) { + super(player); + this.message = message; + this.format = format; + this.recipients = recipients; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the message that the player is attempting to send + * + * @return Message the player is attempting to send + */ + public String getMessage() { + return message; + } + + /** + * Sets the message that the player will send + * + * @param message New message that the player will send + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Sets the player that this message will display as, or command will be + * executed as + * + * @param player New player which this event will execute as + */ + public void setPlayer(final Player player) { + Validate.notNull(player, "Player cannot be null"); + this.player = player; + } + + /** + * Gets the format to use to display this chat message + * + * @return String.Format compatible format string + */ + public String getFormat() { + return format; + } + + /** + * Sets the format to use to display this chat message + * + * @param format String.Format compatible format string + */ + public void setFormat(final String format) { + // Oh for a better way to do this! + try { + String.format(format, player, message); + } catch (RuntimeException ex) { + ex.fillInStackTrace(); + throw ex; + } + + this.format = format; + } + + /** + * Gets a set of recipients that this chat message will be displayed to + * + * @return All Players who will see this chat message + */ + public Set getRecipients() { + return recipients; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java b/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java new file mode 100644 index 00000000..53eb6dcd --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java @@ -0,0 +1,70 @@ +package org.bukkit.event.player; + +import java.util.Collection; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player attempts to tab-complete a chat message. + */ +public class PlayerChatTabCompleteEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String message; + private final String lastToken; + private final Collection completions; + + public PlayerChatTabCompleteEvent(final Player who, final String message, final Collection completions) { + super(who); + Validate.notNull(message, "Message cannot be null"); + Validate.notNull(completions, "Completions cannot be null"); + this.message = message; + int i = message.lastIndexOf(' '); + if (i < 0) { + this.lastToken = message; + } else { + this.lastToken = message.substring(i + 1); + } + this.completions = completions; + } + + /** + * Gets the chat message being tab-completed. + * + * @return the chat message + */ + public String getChatMessage() { + return message; + } + + /** + * Gets the last 'token' of the message being tab-completed. + *

    + * The token is the substring starting with the character after the last + * space in the message. + * + * @return The last token for the chat message + */ + public String getLastToken() { + return lastToken; + } + + /** + * This is the collection of completions for this event. + * + * @return the current completions + */ + public Collection getTabCompletions() { + return completions; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java b/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java new file mode 100644 index 00000000..6233cf3f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java @@ -0,0 +1,139 @@ +package org.bukkit.event.player; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * This event is called whenever a player runs a command (by placing a slash + * at the start of their message). It is called early in the command handling + * process, and modifications in this event (via {@link #setMessage(String)}) + * will be shown in the behavior. + *

    + * Many plugins will have no use for this event, and you should + * attempt to avoid using it if it is not necessary. + *

    + * Some examples of valid uses for this event are: + *

      + *
    • Logging executed commands to a separate file + *
    • Variable substitution. For example, replacing + * ${nearbyPlayer} with the name of the nearest other + * player, or simulating the @a and @p + * decorators used by Command Blocks in plugins that do not handle it. + *
    • Conditionally blocking commands belonging to other plugins. For + * example, blocking the use of the /home command in a + * combat arena. + *
    • Per-sender command aliases. For example, after a player runs the + * command /calias cr gamemode creative, the next time they + * run /cr, it gets replaced into + * /gamemode creative. (Global command aliases should be + * done by registering the alias.) + *
    + *

    + * Examples of incorrect uses are: + *

      + *
    • Using this event to run command logic + *
    + *

    + * If the event is cancelled, processing of the command will halt. + *

    + * The state of whether or not there is a slash (/) at the + * beginning of the message should be preserved. If a slash is added or + * removed, unexpected behavior may result. + */ +public class PlayerCommandPreprocessEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private final Set recipients; + + public PlayerCommandPreprocessEvent(final Player player, final String message) { + super(player); + this.recipients = new HashSet(player.getServer().getOnlinePlayers()); + this.message = message; + } + + public PlayerCommandPreprocessEvent(final Player player, final String message, final Set recipients) { + super(player); + this.recipients = recipients; + this.message = message; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the command that the player is attempting to send. + *

    + * All commands begin with a special character; implementations do not + * consider the first character when executing the content. + * + * @return Message the player is attempting to send + */ + public String getMessage() { + return message; + } + + /** + * Sets the command that the player will send. + *

    + * All commands begin with a special character; implementations do not + * consider the first character when executing the content. + * + * @param command New message that the player will send + * @throws IllegalArgumentException if command is null or empty + */ + public void setMessage(String command) throws IllegalArgumentException { + Validate.notNull(command, "Command cannot be null"); + Validate.notEmpty(command, "Command cannot be empty"); + this.message = command; + } + + /** + * Sets the player that this command will be executed as. + * + * @param player New player which this event will execute as + * @throws IllegalArgumentException if the player provided is null + */ + public void setPlayer(final Player player) throws IllegalArgumentException { + Validate.notNull(player, "Player cannot be null"); + this.player = player; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

    + * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. Listeners + * should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @deprecated This method is provided for backward compatibility with no + * guarantee to the effect of viewing or modifying the set. + * @return All Players who will see this chat message + */ + @Deprecated + public Set getRecipients() { + return recipients; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java b/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java new file mode 100644 index 00000000..5b41b652 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a player drops an item from their inventory + */ +public class PlayerDropItemEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item drop; + private boolean cancel = false; + + public PlayerDropItemEvent(final Player player, final Item drop) { + super(player); + this.drop = drop; + } + + /** + * Gets the ItemDrop created by the player + * + * @return ItemDrop created by the player + */ + public Item getItemDrop() { + return drop; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java b/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java new file mode 100644 index 00000000..68d41276 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java @@ -0,0 +1,124 @@ +package org.bukkit.event.player; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.meta.BookMeta; + +/** + * Called when a player edits or signs a book and quill item. If the event is + * cancelled, no changes are made to the BookMeta + */ +public class PlayerEditBookEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final BookMeta previousBookMeta; + private final int slot; + private BookMeta newBookMeta; + private boolean isSigning; + private boolean cancel; + + public PlayerEditBookEvent(Player who, int slot, BookMeta previousBookMeta, BookMeta newBookMeta, boolean isSigning) { + super(who); + + Validate.isTrue(slot >= 0 && slot <= 8, "Slot must be in range 0-8 inclusive"); + Validate.notNull(previousBookMeta, "Previous book meta must not be null"); + Validate.notNull(newBookMeta, "New book meta must not be null"); + + Bukkit.getItemFactory().equals(previousBookMeta, newBookMeta); + + this.previousBookMeta = previousBookMeta; + this.newBookMeta = newBookMeta; + this.slot = slot; + this.isSigning = isSigning; + this.cancel = false; + } + + /** + * Gets the book meta currently on the book. + *

    + * Note: this is a copy of the book meta. You cannot use this object to + * change the existing book meta. + * + * @return the book meta currently on the book + */ + public BookMeta getPreviousBookMeta() { + return previousBookMeta.clone(); + } + + /** + * Gets the book meta that the player is attempting to add to the book. + *

    + * Note: this is a copy of the proposed new book meta. Use {@link + * #setNewBookMeta(BookMeta)} to change what will actually be added to the + * book. + * + * @return the book meta that the player is attempting to add + */ + public BookMeta getNewBookMeta() { + return newBookMeta.clone(); + } + + /** + * Gets the inventory slot number for the book item that triggered this + * event. + *

    + * This is a slot number on the player's hotbar in the range 0-8. + * + * @return the inventory slot number that the book item occupies + */ + public int getSlot() { + return slot; + } + + /** + * Sets the book meta that will actually be added to the book. + * + * @param newBookMeta new book meta + * @throws IllegalArgumentException if the new book meta is null + */ + public void setNewBookMeta(BookMeta newBookMeta) throws IllegalArgumentException { + Validate.notNull(newBookMeta, "New book meta must not be null"); + Bukkit.getItemFactory().equals(newBookMeta, null); + this.newBookMeta = newBookMeta.clone(); + } + + /** + * Gets whether or not the book is being signed. If a book is signed the + * Material changes from BOOK_AND_QUILL to WRITTEN_BOOK. + * + * @return true if the book is being signed + */ + public boolean isSigning() { + return isSigning; + } + + /** + * Sets whether or not the book is being signed. If a book is signed the + * Material changes from BOOK_AND_QUILL to WRITTEN_BOOK. + * + * @param signing whether or not the book is being signed. + */ + public void setSigning(boolean signing) { + isSigning = signing; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java b/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java new file mode 100644 index 00000000..4b5075b0 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java @@ -0,0 +1,109 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Egg; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player throws an egg and it might hatch + */ +public class PlayerEggThrowEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final Egg egg; + private boolean hatching; + private EntityType hatchType; + private byte numHatches; + + public PlayerEggThrowEvent(final Player player, final Egg egg, final boolean hatching, final byte numHatches, final EntityType hatchingType) { + super(player); + this.egg = egg; + this.hatching = hatching; + this.numHatches = numHatches; + this.hatchType = hatchingType; + } + + /** + * Gets the egg involved in this event. + * + * @return the egg involved in this event + */ + public Egg getEgg() { + return egg; + } + + /** + * Gets whether the egg is hatching or not. Will be what the server + * would've done without interaction. + * + * @return boolean Whether the egg is going to hatch or not + */ + public boolean isHatching() { + return hatching; + } + + /** + * Sets whether the egg will hatch or not. + * + * @param hatching true if you want the egg to hatch, false if you want it + * not to + */ + public void setHatching(boolean hatching) { + this.hatching = hatching; + } + + /** + * Get the type of the mob being hatched (EntityType.CHICKEN by default) + * + * @return The type of the mob being hatched by the egg + */ + public EntityType getHatchingType() { + return hatchType; + } + + /** + * Change the type of mob being hatched by the egg + * + * @param hatchType The type of the mob being hatched by the egg + */ + public void setHatchingType(EntityType hatchType) { + if(!hatchType.isSpawnable()) throw new IllegalArgumentException("Can't spawn that entity type from an egg!"); + this.hatchType = hatchType; + } + + /** + * Get the number of mob hatches from the egg. By default the number will + * be the number the server would've done + *

      + *
    • 7/8 chance of being 0 + *
    • 31/256 ~= 1/8 chance to be 1 + *
    • 1/256 chance to be 4 + *
    + * + * @return The number of mobs going to be hatched by the egg + */ + public byte getNumHatches() { + return numHatches; + } + + /** + * Change the number of mobs coming out of the hatched egg + *

    + * The boolean hatching will override this number. Ie. If hatching = + * false, this number will not matter + * + * @param numHatches The number of mobs coming out of the egg + */ + public void setNumHatches(byte numHatches) { + this.numHatches = numHatches; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerEvent.java b/src/main/java/org/bukkit/event/player/PlayerEvent.java new file mode 100644 index 00000000..0d4833f6 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerEvent.java @@ -0,0 +1,30 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; + +/** + * Represents a player related event + */ +public abstract class PlayerEvent extends Event { + protected Player player; + + public PlayerEvent(final Player who) { + player = who; + } + + PlayerEvent(final Player who, boolean async) { + super(async); + player = who; + + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + public final Player getPlayer() { + return player; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java new file mode 100644 index 00000000..f37491d7 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java @@ -0,0 +1,44 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a players experience changes naturally + */ +public class PlayerExpChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private int exp; + + public PlayerExpChangeEvent(final Player player, final int expAmount) { + super(player); + exp = expAmount; + } + + /** + * Get the amount of experience the player will receive + * + * @return The amount of experience + */ + public int getAmount() { + return exp; + } + + /** + * Set the amount of experience the player will receive + * + * @param amount The amount of experience to set + */ + public void setAmount(int amount) { + exp = amount; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerFishEvent.java b/src/main/java/org/bukkit/event/player/PlayerFishEvent.java new file mode 100644 index 00000000..211e304f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerFishEvent.java @@ -0,0 +1,135 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Fish; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; + +/** + * Thrown when a player is fishing + */ +public class PlayerFishEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity entity; + private boolean cancel = false; + private int exp; + private final State state; + private final Fish hookEntity; + + public PlayerFishEvent(final Player player, final Entity entity, final Fish hookEntity, final State state) { + super(player); + this.entity = entity; + this.hookEntity = hookEntity; + this.state = state; + } + + /** + * Gets the entity caught by the player. + *

    + * If player has fished successfully, the result may be cast to {@link + * org.bukkit.entity.Item}. + * + * @return Entity caught by the player, Entity if fishing, and null if + * bobber has gotten stuck in the ground or nothing has been caught + */ + public Entity getCaught() { + return entity; + } + + /** + * Gets the fishing hook. + * + * @return Fish the entity representing the fishing hook/bobber. + */ + public Fish getHook() { + return hookEntity; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the amount of experience received when fishing. + *

    + * Note: This value has no default effect unless the event state is {@link + * State#CAUGHT_FISH}. + * + * @return the amount of experience to drop + */ + public int getExpToDrop() { + return exp; + } + + /** + * Sets the amount of experience received when fishing. + *

    + * Note: This value has no default effect unless the event state is {@link + * State#CAUGHT_FISH}. + * + * @param amount the amount of experience to drop + */ + public void setExpToDrop(int amount) { + exp = amount; + } + + /** + * Gets the state of the fishing + * + * @return A State detailing the state of the fishing + */ + public State getState() { + return state; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the state of the fishing + */ + public enum State { + + /** + * When a player is fishing, ie casting the line out. + */ + FISHING, + /** + * When a player has successfully caught a fish and is reeling it in. In + * this instance, a "fish" is any item retrieved from water as a result + * of fishing, ie an item, but not necessarily a fish. + */ + CAUGHT_FISH, + /** + * When a player has successfully caught an entity. This refers to any + * already spawned entity in the world that has been hooked directly by + * the rod. + */ + CAUGHT_ENTITY, + /** + * When a bobber is stuck in the ground. + */ + IN_GROUND, + /** + * When a player fails to catch anything while fishing usually due to + * poor aiming or timing. + */ + FAILED_ATTEMPT, + /** + * Called when there is a bite on the hook and it is ready to be reeled + * in. + */ + BITE + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java new file mode 100644 index 00000000..8c9afa8d --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.player; + +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when the GameMode of the player is changed. + */ +public class PlayerGameModeChangeEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final GameMode newGameMode; + + public PlayerGameModeChangeEvent(final Player player, final GameMode newGameMode) { + super(player); + this.newGameMode = newGameMode; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the GameMode the player is switched to. + * + * @return player's new GameMode + */ + public GameMode getNewGameMode() { + return newGameMode; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java b/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java new file mode 100644 index 00000000..431715de --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java @@ -0,0 +1,41 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.util.Vector; + +/** + * Represents an event that is called when a player right clicks an entity that + * also contains the location where the entity was clicked. + *
    + * Note that the client may sometimes spuriously send this packet in addition to {@link PlayerInteractEntityEvent}. + * Users are advised to listen to this (parent) class unless specifically required. + */ +public class PlayerInteractAtEntityEvent extends PlayerInteractEntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Vector position; + + public PlayerInteractAtEntityEvent(Player who, Entity clickedEntity, Vector position) { + this(who, clickedEntity, position, EquipmentSlot.HAND); + } + + public PlayerInteractAtEntityEvent(Player who, Entity clickedEntity, Vector position, EquipmentSlot hand) { + super(who, clickedEntity, hand); + this.position = position; + } + + public Vector getClickedPosition() { + return position.clone(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java b/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java new file mode 100644 index 00000000..94609579 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; + +/** + * Represents an event that is called when a player right clicks an entity. + */ +public class PlayerInteractEntityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Entity clickedEntity; + boolean cancelled = false; + private EquipmentSlot hand; + + public PlayerInteractEntityEvent(final Player who, final Entity clickedEntity) { + this(who, clickedEntity, EquipmentSlot.HAND); + } + + public PlayerInteractEntityEvent(final Player who, final Entity clickedEntity, final EquipmentSlot hand) { + super(who); + this.clickedEntity = clickedEntity; + this.hand = hand; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the entity that was rightclicked by the player. + * + * @return entity right clicked by player + */ + public Entity getRightClicked() { + return this.clickedEntity; + } + + /** + * The hand used to perform this interaction. + * + * @return the hand used to interact + */ + public EquipmentSlot getHand() { + return hand; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java b/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java new file mode 100644 index 00000000..0c841fb1 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java @@ -0,0 +1,209 @@ +package org.bukkit.event.player; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.block.Action; +import org.bukkit.inventory.EquipmentSlot; + +/** + * Represents an event that is called when a player interacts with an object or + * air, potentially fired once for each hand. The hand can be determined using + * {@link #getHand()}. + *

    + * This event will fire as cancelled if the vanilla behavior + * is to do nothing (e.g interacting with air) + */ +public class PlayerInteractEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected ItemStack item; + protected Action action; + protected Block blockClicked; + protected BlockFace blockFace; + private Result useClickedBlock; + private Result useItemInHand; + private EquipmentSlot hand; + + public PlayerInteractEvent(final Player who, final Action action, final ItemStack item, final Block clickedBlock, final BlockFace clickedFace) { + this(who, action, item, clickedBlock, clickedFace, EquipmentSlot.HAND); + } + + public PlayerInteractEvent(final Player who, final Action action, final ItemStack item, final Block clickedBlock, final BlockFace clickedFace, final EquipmentSlot hand) { + super(who); + this.action = action; + this.item = item; + this.blockClicked = clickedBlock; + this.blockFace = clickedFace; + this.hand = hand; + + useItemInHand = Result.DEFAULT; + useClickedBlock = clickedBlock == null ? Result.DENY : Result.ALLOW; + } + + /** + * Returns the action type + * + * @return Action returns the type of interaction + */ + public Action getAction() { + return action; + } + + /** + * Gets the cancellation state of this event. Set to true if you want to + * prevent buckets from placing water and so forth + * + * @return boolean cancellation state + */ + public boolean isCancelled() { + return useInteractedBlock() == Result.DENY; + } + + /** + * Sets the cancellation state of this event. A canceled event will not be + * executed in the server, but will still pass to other plugins + *

    + * Canceling this event will prevent use of food (player won't lose the + * food item), prevent bows/snowballs/eggs from firing, etc. (player won't + * lose the ammo) + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + setUseInteractedBlock(cancel ? Result.DENY : useInteractedBlock() == Result.DENY ? Result.DEFAULT : useInteractedBlock()); + setUseItemInHand(cancel ? Result.DENY : useItemInHand() == Result.DENY ? Result.DEFAULT : useItemInHand()); + } + + /** + * Returns the item in hand represented by this event + * + * @return ItemStack the item used + */ + public ItemStack getItem() { + return this.item; + } + + /** + * Convenience method. Returns the material of the item represented by + * this event + * + * @return Material the material of the item used + */ + public Material getMaterial() { + if (!hasItem()) { + return Material.AIR; + } + + return item.getType(); + } + + /** + * Check if this event involved a block + * + * @return boolean true if it did + */ + public boolean hasBlock() { + return this.blockClicked != null; + } + + /** + * Check if this event involved an item + * + * @return boolean true if it did + */ + public boolean hasItem() { + return this.item != null; + } + + /** + * Convenience method to inform the user whether this was a block + * placement event. + * + * @return boolean true if the item in hand was a block + */ + public boolean isBlockInHand() { + if (!hasItem()) { + return false; + } + + return item.getType().isBlock(); + } + + /** + * Returns the clicked block + * + * @return Block returns the block clicked with this item. + */ + public Block getClickedBlock() { + return blockClicked; + } + + /** + * Returns the face of the block that was clicked + * + * @return BlockFace returns the face of the block that was clicked + */ + public BlockFace getBlockFace() { + return blockFace; + } + + /** + * This controls the action to take with the block (if any) that was + * clicked on. This event gets processed for all blocks, but most don't + * have a default action + * + * @return the action to take with the interacted block + */ + public Result useInteractedBlock() { + return useClickedBlock; + } + + /** + * @param useInteractedBlock the action to take with the interacted block + */ + public void setUseInteractedBlock(Result useInteractedBlock) { + this.useClickedBlock = useInteractedBlock; + } + + /** + * This controls the action to take with the item the player is holding. + * This includes both blocks and items (such as flint and steel or + * records). When this is set to default, it will be allowed if no action + * is taken on the interacted block. + * + * @return the action to take with the item in hand + */ + public Result useItemInHand() { + return useItemInHand; + } + + /** + * @param useItemInHand the action to take with the item in hand + */ + public void setUseItemInHand(Result useItemInHand) { + this.useItemInHand = useItemInHand; + } + + /** + * The hand used to perform this interaction. May be null in the case of + * {@link Action#PHYSICAL}. + * + * @return the hand used to interact. May be null. + */ + public EquipmentSlot getHand() { + return hand; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java b/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java new file mode 100644 index 00000000..176cd918 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java @@ -0,0 +1,39 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Fired when a player's item breaks (such as a shovel or flint and steel). + *

    + * The item that's breaking will exist in the inventory with a stack size of + * 0. After the event, the item's durability will be reset to 0. + */ +public class PlayerItemBreakEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack brokenItem; + + public PlayerItemBreakEvent(final Player player, final ItemStack brokenItem) { + super(player); + this.brokenItem = brokenItem; + } + + /** + * Gets the item that broke + * + * @return The broken item + */ + public ItemStack getBrokenItem() { + return brokenItem; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java b/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java new file mode 100644 index 00000000..b8da5ccf --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java @@ -0,0 +1,74 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * This event will fire when a player is finishing consuming an item (food, + * potion, milk bucket). + *
    + * If the ItemStack is modified the server will use the effects of the new + * item and not remove the original one from the player's inventory. + *
    + * If the event is cancelled the effect will not be applied and the item will + * not be removed from the player's inventory. + */ +public class PlayerItemConsumeEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean isCancelled = false; + private ItemStack item; + + /** + * @param player the player consuming + * @param item the ItemStack being consumed + */ + public PlayerItemConsumeEvent(final Player player, final ItemStack item) { + super(player); + + this.item = item; + } + + /** + * Gets the item that is being consumed. Modifying the returned item will + * have no effect, you must use {@link + * #setItem(ItemStack)} instead. + * + * @return an ItemStack for the item being consumed + */ + public ItemStack getItem() { + return item.clone(); + } + + /** + * Set the item being consumed + * + * @param item the item being consumed + */ + public void setItem(ItemStack item) { + if (item == null) { + this.item = new ItemStack(Material.AIR); + } else { + this.item = item; + } + } + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java b/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java new file mode 100644 index 00000000..38a72ab8 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java @@ -0,0 +1,54 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +public class PlayerItemDamageEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final ItemStack item; + private int damage; + private boolean cancelled = false; + + public PlayerItemDamageEvent(Player player, ItemStack what, int damage) { + super(player); + this.item = what; + this.damage = damage; + } + + public ItemStack getItem() { + return item; + } + + /** + * Gets the amount of durability damage this item will be taking. + * + * @return durability change + */ + public int getDamage() { + return damage; + } + + public void setDamage(int damage) { + this.damage = damage; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java b/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java new file mode 100644 index 00000000..f0d055a0 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Fired when a player changes their currently held item + */ +public class PlayerItemHeldEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final int previous; + private final int current; + + public PlayerItemHeldEvent(final Player player, final int previous, final int current) { + super(player); + this.previous = previous; + this.current = current; + } + + /** + * Gets the previous held slot index + * + * @return Previous slot index + */ + public int getPreviousSlot() { + return previous; + } + + /** + * Gets the new held slot index + * + * @return New slot index + */ + public int getNewSlot() { + return current; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java b/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java new file mode 100644 index 00000000..ae9e1c90 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java @@ -0,0 +1,92 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Represents when a player has an item repaired via the Mending enchantment. + *
    + * This event is fired directly before the {@link PlayerExpChangeEvent}, and the + * results of this event directly affect the {@link PlayerExpChangeEvent}. + */ +public class PlayerItemMendEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final ItemStack item; + private final ExperienceOrb experienceOrb; + private int repairAmount; + private boolean cancelled; + + public PlayerItemMendEvent(Player who, ItemStack item, ExperienceOrb experienceOrb, int repairAmount) { + super(who); + this.item = item; + this.experienceOrb = experienceOrb; + this.repairAmount = repairAmount; + } + + /** + * Get the {@link ItemStack} to be repaired. + * + * This is not necessarily the item the player is holding. + * + * @return the item to be repaired + */ + public ItemStack getItem() { + return item; + } + + /** + * Get the experience orb triggering the event. + * + * @return the experience orb + */ + public ExperienceOrb getExperienceOrb() { + return experienceOrb; + } + + /** + * Get the amount the item is to be repaired. + * + * The default value is twice the value of the consumed experience orb + * or the remaining damage left on the item, whichever is smaller. + * + * @return how much damage will be repaired by the experience orb + */ + public int getRepairAmount() { + return repairAmount; + } + + /** + * Set the amount the item will be repaired. + * + * Half of this value will be subtracted from the experience orb which initiated this event. + * + * @param amount how much damage will be repaired on the item + */ + public void setRepairAmount(int amount) { + this.repairAmount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java b/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java new file mode 100644 index 00000000..e7481f92 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java @@ -0,0 +1,44 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player joins a server + */ +public class PlayerJoinEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private String joinMessage; + + public PlayerJoinEvent(final Player playerJoined, final String joinMessage) { + super(playerJoined); + this.joinMessage = joinMessage; + } + + /** + * Gets the join message to send to all online players + * + * @return string join message + */ + public String getJoinMessage() { + return joinMessage; + } + + /** + * Sets the join message to send to all online players + * + * @param joinMessage join message + */ + public void setJoinMessage(String joinMessage) { + this.joinMessage = joinMessage; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerKickEvent.java b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java new file mode 100644 index 00000000..39e81b67 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player gets kicked from the server + */ +public class PlayerKickEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private String leaveMessage; + private String kickReason; + private Boolean cancel; + + public PlayerKickEvent(final Player playerKicked, final String kickReason, final String leaveMessage) { + super(playerKicked); + this.kickReason = kickReason; + this.leaveMessage = leaveMessage; + this.cancel = false; + } + + /** + * Gets the reason why the player is getting kicked + * + * @return string kick reason + */ + public String getReason() { + return kickReason; + } + + /** + * Gets the leave message send to all online players + * + * @return string kick reason + */ + public String getLeaveMessage() { + return leaveMessage; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Sets the reason why the player is getting kicked + * + * @param kickReason kick reason + */ + public void setReason(String kickReason) { + this.kickReason = kickReason; + } + + /** + * Sets the leave message send to all online players + * + * @param leaveMessage leave message + */ + public void setLeaveMessage(String leaveMessage) { + this.leaveMessage = leaveMessage; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java new file mode 100644 index 00000000..68254279 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a players level changes + */ +public class PlayerLevelChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final int oldLevel; + private final int newLevel; + + public PlayerLevelChangeEvent(final Player player, final int oldLevel, final int newLevel) { + super(player); + this.oldLevel = oldLevel; + this.newLevel = newLevel; + } + + /** + * Gets the old level of the player + * + * @return The old level of the player + */ + public int getOldLevel() { + return oldLevel; + } + + /** + * Gets the new level of the player + * + * @return The new (current) level of the player + */ + public int getNewLevel() { + return newLevel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java new file mode 100644 index 00000000..fbbfcccc --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player changes their locale in the client settings. + */ +public class PlayerLocaleChangeEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final String locale; + + public PlayerLocaleChangeEvent(Player who, String locale) { + super(who); + this.locale = locale; + } + + /** + * @see Player#getLocale() + * + * @return the player's new locale + */ + public String getLocale() { + return locale; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java b/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java new file mode 100644 index 00000000..de4fb37f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java @@ -0,0 +1,182 @@ +package org.bukkit.event.player; + +import java.net.InetAddress; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Stores details for players attempting to log in + */ +public class PlayerLoginEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final InetAddress address; + private final String hostname; + private Result result = Result.ALLOWED; + private String message = ""; + private final InetAddress realAddress; // Spigot + + /** + * This constructor defaults message to an empty string, and result to + * ALLOWED + * + * @param player The {@link Player} for this event + * @param hostname The hostname that was used to connect to the server + * @param address The address the player used to connect, provided for + * timing issues + */ + public PlayerLoginEvent(final Player player, final String hostname, final InetAddress address, final InetAddress realAddress) { // Spigot + super(player); + this.hostname = hostname; + this.address = address; + // Spigot start + this.realAddress = realAddress; + } + + public PlayerLoginEvent(final Player player, final String hostname, final InetAddress address) { + this(player, hostname, address, address); + // Spigot end + } + + /** + * This constructor pre-configures the event with a result and message + * + * @param player The {@link Player} for this event + * @param hostname The hostname that was used to connect to the server + * @param address The address the player used to connect, provided for + * timing issues + * @param result The result status for this event + * @param message The message to be displayed if result denies login + */ + public PlayerLoginEvent(final Player player, String hostname, final InetAddress address, final Result result, final String message, final InetAddress realAddress) { // Spigot + this(player, hostname, address, realAddress); // Spigot + this.result = result; + this.message = message; + } + + // Spigot start + /** + * Gets the connection address of this player, regardless of whether it has been spoofed or not. + * + * @return the player's connection address + */ + public InetAddress getRealAddress() { + return realAddress; + } + // Spigot end + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + public Result getResult() { + return result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setResult(final Result result) { + this.result = result; + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(final String message) { + this.message = message; + } + + /** + * Gets the hostname that the player used to connect to the server, or + * blank if unknown + * + * @return The hostname + */ + public String getHostname() { + return hostname; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(final Result result, final String message) { + this.result = result; + this.message = message; + } + + /** + * Gets the {@link InetAddress} for the Player associated with this event. + * This method is provided as a workaround for player.getAddress() + * returning null during PlayerLoginEvent. + * + * @return The address for this player. For legacy compatibility, this may + * be null. + */ + public InetAddress getAddress() { + return address; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java b/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java new file mode 100644 index 00000000..d56b7e40 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java @@ -0,0 +1,103 @@ +package org.bukkit.event.player; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Holds information for player movement events + */ +public class PlayerMoveEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Location from; + private Location to; + + public PlayerMoveEvent(final Player player, final Location from, final Location to) { + super(player); + this.from = from; + this.to = to; + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

    + * If a move or teleport event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @return true if this event is cancelled + */ + public boolean isCancelled() { + return cancel; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

    + * If a move or teleport event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location this player moved from + * + * @return Location the player moved from + */ + public Location getFrom() { + return from; + } + + /** + * Sets the location to mark as where the player moved from + * + * @param from New location to mark as the players previous location + */ + public void setFrom(Location from) { + validateLocation(from); + this.from = from; + } + + /** + * Gets the location this player moved to + * + * @return Location the player moved to + */ + public Location getTo() { + return to; + } + + /** + * Sets the location that this player will move to + * + * @param to New Location this player will move to + */ + public void setTo(Location to) { + validateLocation(to); + this.to = to; + } + + private void validateLocation(Location loc) { + Preconditions.checkArgument(loc != null, "Cannot use null location!"); + Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!"); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java b/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java new file mode 100644 index 00000000..97399ed6 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; + +/** + * Thrown when a player picks up an arrow from the ground. + */ +public class PlayerPickupArrowEvent extends PlayerPickupItemEvent { + + private final Arrow arrow; + + public PlayerPickupArrowEvent(final Player player, final Item item, final Arrow arrow) { + super(player, item, 0); + this.arrow = arrow; + } + + /** + * Get the arrow being picked up by the player + * + * @return The arrow being picked up + */ + public Arrow getArrow() { + return arrow; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java b/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java new file mode 100644 index 00000000..c76f423e --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.player; + +import org.bukkit.Warning; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityPickupItemEvent; + +/** + * Thrown when a player picks an item up from the ground + * @deprecated {@link EntityPickupItemEvent} + */ +@Deprecated +@Warning(false) +public class PlayerPickupItemEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item item; + private boolean cancel = false; + private final int remaining; + + public PlayerPickupItemEvent(final Player player, final Item item, final int remaining) { + super(player); + this.item = item; + this.remaining = remaining; + } + + /** + * Gets the Item picked up by the player. + * + * @return Item + */ + public Item getItem() { + return item; + } + + /** + * Gets the amount remaining on the ground, if any + * + * @return amount remaining on the ground + */ + public int getRemaining() { + return remaining; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java b/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java new file mode 100644 index 00000000..330311df --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java @@ -0,0 +1,87 @@ +package org.bukkit.event.player; + +import org.bukkit.Location; +import org.bukkit.TravelAgent; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player is about to teleport because it is in contact with a + * portal. + *

    + * For other entities see {@link org.bukkit.event.entity.EntityPortalEvent} + */ +public class PlayerPortalEvent extends PlayerTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean useTravelAgent = true; + protected TravelAgent travelAgent; + + public PlayerPortalEvent(final Player player, final Location from, final Location to, final TravelAgent pta) { + super(player, from, to); + this.travelAgent = pta; + } + + public PlayerPortalEvent(Player player, Location from, Location to, TravelAgent pta, TeleportCause cause) { + super(player, from, to, cause); + this.travelAgent = pta; + } + + /** + * Sets whether or not the Travel Agent will be used. + *

    + * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

    + * If this is set to false, the {@link #getPlayer()} will only be + * teleported to the {@link #getTo()} Location. + * + * @param useTravelAgent whether to use the Travel Agent + */ + public void useTravelAgent(boolean useTravelAgent) { + this.useTravelAgent = useTravelAgent; + } + + /** + * Gets whether or not the Travel Agent will be used. + *

    + * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

    + * If this is set to false, the {@link #getPlayer()}} will only be + * teleported to the {@link #getTo()} Location. + * + * @return whether to use the Travel Agent + */ + public boolean useTravelAgent() { + return useTravelAgent && travelAgent != null; + } + + /** + * Gets the Travel Agent used (or not) in this event. + * + * @return the Travel Agent used (or not) in this event + */ + public TravelAgent getPortalTravelAgent() { + return this.travelAgent; + } + + /** + * Sets the Travel Agent used (or not) in this event. + * + * @param travelAgent the Travel Agent used (or not) in this event + */ + public void setPortalTravelAgent(TravelAgent travelAgent) { + this.travelAgent = travelAgent; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java b/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java new file mode 100644 index 00000000..e8553f0f --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java @@ -0,0 +1,159 @@ +package org.bukkit.event.player; + +import java.net.InetAddress; +import java.util.UUID; + +import org.bukkit.Warning; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Stores details for players attempting to log in + * + * @deprecated This event causes synchronization from the login thread; {@link + * AsyncPlayerPreLoginEvent} is preferred to keep the secondary threads + * asynchronous. + */ +@Deprecated +@Warning(reason="This event causes a login thread to synchronize with the main thread") +public class PlayerPreLoginEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private Result result; + private String message; + private final String name; + private final InetAddress ipAddress; + private final UUID uniqueId; + + @Deprecated + public PlayerPreLoginEvent(final String name, final InetAddress ipAddress) { + this(name, ipAddress, null); + } + + public PlayerPreLoginEvent(final String name, final InetAddress ipAddress, final UUID uniqueId) { + this.result = Result.ALLOWED; + this.message = ""; + this.name = name; + this.ipAddress = ipAddress; + this.uniqueId = uniqueId; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + public Result getResult() { + return result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setResult(final Result result) { + this.result = result; + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(final String message) { + this.message = message; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(final Result result, final String message) { + this.result = result; + this.message = message; + } + + /** + * Gets the player's name. + * + * @return the player's name + */ + public String getName() { + return name; + } + + /** + * Gets the player IP address. + * + * @return The IP address + */ + public InetAddress getAddress() { + return ipAddress; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + /** + * Gets the player's unique ID. + * + * @return The unique ID + */ + public UUID getUniqueId() { + return uniqueId; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java new file mode 100644 index 00000000..5c8dc1b9 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java @@ -0,0 +1,44 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player leaves a server + */ +public class PlayerQuitEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private String quitMessage; + + public PlayerQuitEvent(final Player who, final String quitMessage) { + super(who); + this.quitMessage = quitMessage; + } + + /** + * Gets the quit message to send to all online players + * + * @return string quit message + */ + public String getQuitMessage() { + return quitMessage; + } + + /** + * Sets the quit message to send to all online players + * + * @param quitMessage quit message + */ + public void setQuitMessage(String quitMessage) { + this.quitMessage = quitMessage; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java new file mode 100644 index 00000000..442ac7fb --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java @@ -0,0 +1,13 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; + +/** + * This is called immediately after a player registers for a plugin channel. + */ +public class PlayerRegisterChannelEvent extends PlayerChannelEvent { + + public PlayerRegisterChannelEvent(final Player player, final String channel) { + super(player, channel); + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java b/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java new file mode 100644 index 00000000..ad433444 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java @@ -0,0 +1,61 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player takes action on a resource pack request sent via + * {@link Player#setResourcePack(String)}. + */ +public class PlayerResourcePackStatusEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + private final Status status; + + public PlayerResourcePackStatusEvent(final Player who, Status resourcePackStatus) { + super(who); + this.status = resourcePackStatus; + } + + /** + * Gets the status of this pack. + * + * @return the current status + */ + public Status getStatus() { + return status; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Status of the resource pack. + */ + public enum Status { + + /** + * The resource pack has been successfully downloaded and applied to the + * client. + */ + SUCCESSFULLY_LOADED, + /** + * The client refused to accept the resource pack. + */ + DECLINED, + /** + * The client accepted the pack, but download failed. + */ + FAILED_DOWNLOAD, + /** + * The client accepted the pack and is beginning a download of it. + */ + ACCEPTED; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java b/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java new file mode 100644 index 00000000..24bbb708 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java @@ -0,0 +1,60 @@ +package org.bukkit.event.player; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Called when a player respawns. + */ +public class PlayerRespawnEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private Location respawnLocation; + private final boolean isBedSpawn; + + public PlayerRespawnEvent(final Player respawnPlayer, final Location respawnLocation, final boolean isBedSpawn) { + super(respawnPlayer); + this.respawnLocation = respawnLocation; + this.isBedSpawn = isBedSpawn; + } + + /** + * Gets the current respawn location + * + * @return Location current respawn location + */ + public Location getRespawnLocation() { + return this.respawnLocation; + } + + /** + * Sets the new respawn location + * + * @param respawnLocation new location for the respawn + */ + public void setRespawnLocation(Location respawnLocation) { + Validate.notNull(respawnLocation, "Respawn location can not be null"); + Validate.notNull(respawnLocation.getWorld(), "Respawn world can not be null"); + + this.respawnLocation = respawnLocation; + } + + /** + * Gets whether the respawn location is the player's bed. + * + * @return true if the respawn location is the player's bed. + */ + public boolean isBedSpawn() { + return this.isBedSpawn; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java b/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java new file mode 100644 index 00000000..38afb3ce --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player shears an entity + */ +public class PlayerShearEntityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final Entity what; + + public PlayerShearEntityEvent(final Player who, final Entity what) { + super(who); + this.cancel = false; + this.what = what; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the entity the player is shearing + * + * @return the entity the player is shearing + */ + public Entity getEntity() { + return what; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java b/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java new file mode 100644 index 00000000..1da2f4a9 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java @@ -0,0 +1,116 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player statistic is incremented. + *

    + * This event is not called for {@link Statistic#PLAY_ONE_TICK} or + * movement based statistics. + * + */ +public class PlayerStatisticIncrementEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected final Statistic statistic; + private final int initialValue; + private final int newValue; + private boolean isCancelled = false; + private final EntityType entityType; + private final Material material; + + public PlayerStatisticIncrementEvent(Player player, Statistic statistic, int initialValue, int newValue) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = null; + this.material = null; + } + + public PlayerStatisticIncrementEvent(Player player, Statistic statistic, int initialValue, int newValue, EntityType entityType) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = entityType; + this.material = null; + } + + public PlayerStatisticIncrementEvent(Player player, Statistic statistic, int initialValue, int newValue, Material material) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = null; + this.material = material; + } + + /** + * Gets the statistic that is being incremented. + * + * @return the incremented statistic + */ + public Statistic getStatistic() { + return statistic; + } + + /** + * Gets the previous value of the statistic. + * + * @return the previous value of the statistic + */ + public int getPreviousValue() { + return initialValue; + } + + /** + * Gets the new value of the statistic. + * + * @return the new value of the statistic + */ + public int getNewValue() { + return newValue; + } + + /** + * Gets the EntityType if {@link #getStatistic() getStatistic()} is an + * entity statistic otherwise returns null. + * + * @return the EntityType of the statistic + */ + public EntityType getEntityType() { + return entityType; + } + + /** + * Gets the Material if {@link #getStatistic() getStatistic()} is a block + * or item statistic otherwise returns null. + * + * @return the Material of the statistic + */ + public Material getMaterial() { + return material; + } + + public boolean isCancelled() { + return isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java b/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java new file mode 100644 index 00000000..483eb17e --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java @@ -0,0 +1,81 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +/** + * Called when a player swap items between main hand and off hand using the + * hotkey. + */ +public class PlayerSwapHandItemsEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private ItemStack mainHandItem; + private ItemStack offHandItem; + private boolean cancelled; + + public PlayerSwapHandItemsEvent(Player player, ItemStack mainHandItem, ItemStack offHandItem) { + super(player); + + this.mainHandItem = mainHandItem; + this.offHandItem = offHandItem; + } + + /** + * Gets the item switched to the main hand. + * + * @return item in the main hand + */ + public ItemStack getMainHandItem() { + return mainHandItem; + } + + /** + * Sets the item in the main hand. + * + * @param mainHandItem new item in the main hand + */ + public void setMainHandItem(ItemStack mainHandItem) { + this.mainHandItem = mainHandItem; + } + + /** + * Gets the item switched to the off hand. + * + * @return item in the off hand + */ + public ItemStack getOffHandItem() { + return offHandItem; + } + + /** + * Sets the item in the off hand. + * + * @param offHandItem new item in the off hand + */ + public void setOffHandItem(ItemStack offHandItem) { + this.offHandItem = offHandItem; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java new file mode 100644 index 00000000..caab3da1 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java @@ -0,0 +1,93 @@ +package org.bukkit.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +/** + * Holds information for player teleport events + */ +public class PlayerTeleportEvent extends PlayerMoveEvent { + private static final HandlerList handlers = new HandlerList(); + private TeleportCause cause = TeleportCause.UNKNOWN; + + public PlayerTeleportEvent(final Player player, final Location from, final Location to) { + super(player, from, to); + } + + public PlayerTeleportEvent(final Player player, final Location from, final Location to, final TeleportCause cause) { + this(player, from, to); + + this.cause = cause; + } + + /** + * Gets the cause of this teleportation event + * + * @return Cause of the event + */ + public TeleportCause getCause() { + return cause; + } + + public enum TeleportCause { + /** + * Indicates the teleporation was caused by a player throwing an Ender + * Pearl + */ + ENDER_PEARL, + /** + * Indicates the teleportation was caused by a player executing a + * command + */ + COMMAND, + /** + * Indicates the teleportation was caused by a plugin + */ + PLUGIN, + /** + * Indicates the teleportation was caused by a player entering a + * Nether portal + */ + NETHER_PORTAL, + /** + * Indicates the teleportation was caused by a player entering an End + * portal + */ + END_PORTAL, + /** + * Indicates the teleportation was caused by a player teleporting to a + * Entity/Player via the spectator menu + */ + SPECTATE, + /** + * Indicates the teleportation was caused by a player entering an End + * gateway + */ + END_GATEWAY, + /** + * Indicates the teleportation was caused by a player consuming chorus + * fruit + */ + CHORUS_FRUIT, + /** + * Indicates the teleportation was caused by a player entering a + * Mod portal + */ + MOD, + /** + * Indicates the teleportation was caused by an event not covered by + * this enum + */ + UNKNOWN; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java b/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java new file mode 100644 index 00000000..1c5ec37e --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player toggles their flying state + */ +public class PlayerToggleFlightEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isFlying; + private boolean cancel = false; + + public PlayerToggleFlightEvent(final Player player, final boolean isFlying) { + super(player); + this.isFlying = isFlying; + } + + /** + * Returns whether the player is trying to start or stop flying. + * + * @return flying state + */ + public boolean isFlying() { + return isFlying; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java b/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java new file mode 100644 index 00000000..667acad2 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player toggles their sneaking state + */ +public class PlayerToggleSneakEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isSneaking; + private boolean cancel = false; + + public PlayerToggleSneakEvent(final Player player, final boolean isSneaking) { + super(player); + this.isSneaking = isSneaking; + } + + /** + * Returns whether the player is now sneaking or not. + * + * @return sneaking state + */ + public boolean isSneaking() { + return isSneaking; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java b/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java new file mode 100644 index 00000000..1558f8f7 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a player toggles their sprinting state + */ +public class PlayerToggleSprintEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isSprinting; + private boolean cancel = false; + + public PlayerToggleSprintEvent(final Player player, final boolean isSprinting) { + super(player); + this.isSprinting = isSprinting; + } + + /** + * Gets whether the player is now sprinting or not. + * + * @return sprinting state + */ + public boolean isSprinting() { + return isSprinting; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java b/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java new file mode 100644 index 00000000..f6aebefb --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.EntityUnleashEvent; + +/** + * Called prior to an entity being unleashed due to a player's action. + */ +public class PlayerUnleashEntityEvent extends EntityUnleashEvent implements Cancellable { + private final Player player; + private boolean cancelled = false; + + public PlayerUnleashEntityEvent(Entity entity, Player player) { + super(entity, UnleashReason.PLAYER_UNLEASH); + this.player = player; + } + + /** + * Returns the player who is unleashing the entity. + * + * @return The player + */ + public Player getPlayer() { + return player; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java new file mode 100644 index 00000000..11c77e35 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java @@ -0,0 +1,13 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; + +/** + * This is called immediately after a player unregisters for a plugin channel. + */ +public class PlayerUnregisterChannelEvent extends PlayerChannelEvent { + + public PlayerUnregisterChannelEvent(final Player player, final String channel) { + super(player, channel); + } +} diff --git a/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java b/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java new file mode 100644 index 00000000..69d2fce4 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java @@ -0,0 +1,55 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.util.Vector; + +/** + * Called when the velocity of a player changes. + */ +public class PlayerVelocityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Vector velocity; + + public PlayerVelocityEvent(final Player player, final Vector velocity) { + super(player); + this.velocity = velocity; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the velocity vector that will be sent to the player + * + * @return Vector the player will get + */ + public Vector getVelocity() { + return velocity; + } + + /** + * Sets the velocity vector that will be sent to the player + * + * @param velocity The velocity vector that will be sent to the player + */ + public void setVelocity(Vector velocity) { + this.velocity = velocity; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java b/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java new file mode 100644 index 00000000..ae563114 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java @@ -0,0 +1,77 @@ +package org.bukkit.event.server; + +import java.util.Set; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Event triggered for server broadcast messages such as from + * {@link org.bukkit.Server#broadcast(String, String)}. + */ +public class BroadcastMessageEvent extends ServerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private String message; + private final Set recipients; + private boolean cancelled = false; + + public BroadcastMessageEvent(String message, Set recipients) { + this.message = message; + this.recipients = recipients; + } + + /** + * Get the message to broadcast. + * + * @return Message to broadcast + */ + public String getMessage() { + return message; + } + + /** + * Set the message to broadcast. + * + * @param message New message to broadcast + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

    + * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. + *

    + * Listeners should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @return All CommandSenders who will see this chat message + */ + public Set getRecipients() { + return recipients; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/MapInitializeEvent.java b/src/main/java/org/bukkit/event/server/MapInitializeEvent.java new file mode 100644 index 00000000..8834489b --- /dev/null +++ b/src/main/java/org/bukkit/event/server/MapInitializeEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.map.MapView; + +/** + * Called when a map is initialized. + */ +public class MapInitializeEvent extends ServerEvent { + private static final HandlerList handlers = new HandlerList(); + private final MapView mapView; + + public MapInitializeEvent(final MapView mapView) { + this.mapView = mapView; + } + + /** + * Gets the map initialized in this event. + * + * @return Map for this event + */ + public MapView getMap() { + return mapView; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/PluginDisableEvent.java b/src/main/java/org/bukkit/event/server/PluginDisableEvent.java new file mode 100644 index 00000000..932c4fda --- /dev/null +++ b/src/main/java/org/bukkit/event/server/PluginDisableEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; + +/** + * Called when a plugin is disabled. + */ +public class PluginDisableEvent extends PluginEvent { + private static final HandlerList handlers = new HandlerList(); + + public PluginDisableEvent(final Plugin plugin) { + super(plugin); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/PluginEnableEvent.java b/src/main/java/org/bukkit/event/server/PluginEnableEvent.java new file mode 100644 index 00000000..865316de --- /dev/null +++ b/src/main/java/org/bukkit/event/server/PluginEnableEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; + +/** + * Called when a plugin is enabled. + */ +public class PluginEnableEvent extends PluginEvent { + private static final HandlerList handlers = new HandlerList(); + + public PluginEnableEvent(final Plugin plugin) { + super(plugin); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/PluginEvent.java b/src/main/java/org/bukkit/event/server/PluginEvent.java new file mode 100644 index 00000000..1ad656d6 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/PluginEvent.java @@ -0,0 +1,23 @@ +package org.bukkit.event.server; + +import org.bukkit.plugin.Plugin; + +/** + * Used for plugin enable and disable events + */ +public abstract class PluginEvent extends ServerEvent { + private final Plugin plugin; + + public PluginEvent(final Plugin plugin) { + this.plugin = plugin; + } + + /** + * Gets the plugin involved in this event + * + * @return Plugin for this event + */ + public Plugin getPlugin() { + return plugin; + } +} diff --git a/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java b/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java new file mode 100644 index 00000000..80ab4a9a --- /dev/null +++ b/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java @@ -0,0 +1,25 @@ +package org.bukkit.event.server; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; + +/** + * This event is called when a command is received over RCON. See the javadocs + * of {@link ServerCommandEvent} for more information. + */ +public class RemoteServerCommandEvent extends ServerCommandEvent { + private static final HandlerList handlers = new HandlerList(); + + public RemoteServerCommandEvent(final CommandSender sender, final String command) { + super(sender, command); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/ServerCommandEvent.java b/src/main/java/org/bukkit/event/server/ServerCommandEvent.java new file mode 100644 index 00000000..21250d4d --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServerCommandEvent.java @@ -0,0 +1,98 @@ +package org.bukkit.event.server; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * This event is called when a command is run by a non-player. It is + * called early in the command handling process, and modifications in this + * event (via {@link #setCommand(String)}) will be shown in the behavior. + *

    + * Many plugins will have no use for this event, and you should + * attempt to avoid using it if it is not necessary. + *

    + * Some examples of valid uses for this event are: + *

      + *
    • Logging executed commands to a separate file + *
    • Variable substitution. For example, replacing ${ip:Steve} + * with the connection IP of the player named Steve, or simulating the + * @a and @p decorators used by Command Blocks + * for plugins that do not handle it. + *
    • Conditionally blocking commands belonging to other plugins. + *
    • Per-sender command aliases. For example, after the console runs the + * command /calias cr gamemode creative, the next time they + * run /cr, it gets replaced into + * /gamemode creative. (Global command aliases should be + * done by registering the alias.) + *
    + *

    + * Examples of incorrect uses are: + *

      + *
    • Using this event to run command logic + *
    + *

    + * If the event is cancelled, processing of the command will halt. + *

    + * The state of whether or not there is a slash (/) at the + * beginning of the message should be preserved. If a slash is added or + * removed, unexpected behavior may result. + */ +public class ServerCommandEvent extends ServerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private String command; + private final CommandSender sender; + private boolean cancel = false; + + public ServerCommandEvent(final CommandSender sender, final String command) { + this.command = command; + this.sender = sender; + } + + /** + * Gets the command that the user is attempting to execute from the + * console + * + * @return Command the user is attempting to execute + */ + public String getCommand() { + return command; + } + + /** + * Sets the command that the server will execute + * + * @param message New message that the server will execute + */ + public void setCommand(String message) { + this.command = message; + } + + /** + * Get the command sender. + * + * @return The sender + */ + public CommandSender getSender() { + return sender; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/src/main/java/org/bukkit/event/server/ServerEvent.java b/src/main/java/org/bukkit/event/server/ServerEvent.java new file mode 100644 index 00000000..eb00d6af --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServerEvent.java @@ -0,0 +1,9 @@ +package org.bukkit.event.server; + +import org.bukkit.event.Event; + +/** + * Miscellaneous server events + */ +public abstract class ServerEvent extends Event { +} diff --git a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java new file mode 100644 index 00000000..1b7e56d2 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java @@ -0,0 +1,146 @@ +package org.bukkit.event.server; + +import java.net.InetAddress; +import java.util.Iterator; + +import org.apache.commons.lang3.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.util.CachedServerIcon; + +/** + * Called when a server list ping is coming in. Displayed players can be + * checked and removed by {@link #iterator() iterating} over this event. + */ +public class ServerListPingEvent extends ServerEvent implements Iterable { + private static final int MAGIC_PLAYER_COUNT = Integer.MIN_VALUE; + private static final HandlerList handlers = new HandlerList(); + private final InetAddress address; + private String motd; + private final int numPlayers; + private int maxPlayers; + + public ServerListPingEvent(final InetAddress address, final String motd, final int numPlayers, final int maxPlayers) { + Validate.isTrue(numPlayers >= 0, "Cannot have negative number of players online", numPlayers); + this.address = address; + this.motd = motd; + this.numPlayers = numPlayers; + this.maxPlayers = maxPlayers; + } + + /** + * This constructor is intended for implementations that provide the + * {@link #iterator()} method, thus provided the {@link #getNumPlayers()} + * count. + * + * @param address the address of the pinger + * @param motd the message of the day + * @param maxPlayers the max number of players + */ + protected ServerListPingEvent(final InetAddress address, final String motd, final int maxPlayers) { + this.numPlayers = MAGIC_PLAYER_COUNT; + this.address = address; + this.motd = motd; + this.maxPlayers = maxPlayers; + } + + /** + * Get the address the ping is coming from. + * + * @return the address + */ + public InetAddress getAddress() { + return address; + } + + /** + * Get the message of the day message. + * + * @return the message of the day + */ + public String getMotd() { + return motd; + } + + /** + * Change the message of the day message. + * + * @param motd the message of the day + */ + public void setMotd(String motd) { + this.motd = motd; + } + + /** + * Get the number of players sent. + * + * @return the number of players + */ + public int getNumPlayers() { + int numPlayers = this.numPlayers; + if (numPlayers == MAGIC_PLAYER_COUNT) { + numPlayers = 0; + for (@SuppressWarnings("unused") final Player player : this) { + numPlayers++; + } + } + return numPlayers; + } + + /** + * Get the maximum number of players sent. + * + * @return the maximum number of players + */ + public int getMaxPlayers() { + return maxPlayers; + } + + /** + * Set the maximum number of players sent. + * + * @param maxPlayers the maximum number of player + */ + public void setMaxPlayers(int maxPlayers) { + this.maxPlayers = maxPlayers; + } + + /** + * Sets the server-icon sent to the client. + * + * @param icon the icon to send to the client + * @throws IllegalArgumentException if the {@link CachedServerIcon} is not + * created by the caller of this event; null may be accepted for some + * implementations + * @throws UnsupportedOperationException if the caller of this event does + * not support setting the server icon + */ + public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * {@inheritDoc} + *

    + * Calling the {@link Iterator#remove()} method will force that particular + * player to not be displayed on the player list, decrease the size + * returned by {@link #getNumPlayers()}, and will not be returned again by + * any new iterator. + * + * @throws UnsupportedOperationException if the caller of this event does + * not support removing players + */ + @Override + public Iterator iterator() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/bukkit/event/server/ServiceEvent.java b/src/main/java/org/bukkit/event/server/ServiceEvent.java new file mode 100644 index 00000000..69bf8723 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServiceEvent.java @@ -0,0 +1,19 @@ +package org.bukkit.event.server; + +import org.bukkit.plugin.RegisteredServiceProvider; + +/** + * An event relating to a registered service. This is called in a {@link + * org.bukkit.plugin.ServicesManager} + */ +public abstract class ServiceEvent extends ServerEvent { + private final RegisteredServiceProvider provider; + + public ServiceEvent(final RegisteredServiceProvider provider) { + this.provider = provider; + } + + public RegisteredServiceProvider getProvider() { + return provider; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java b/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java new file mode 100644 index 00000000..7dfadde1 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.RegisteredServiceProvider; + +/** + * This event is called when a service is registered. + *

    + * Warning: The order in which register and unregister events are called + * should not be relied upon. + */ +public class ServiceRegisterEvent extends ServiceEvent { + private static final HandlerList handlers = new HandlerList(); + + public ServiceRegisterEvent(RegisteredServiceProvider registeredProvider) { + super(registeredProvider); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java b/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java new file mode 100644 index 00000000..db61d236 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.RegisteredServiceProvider; + +/** + * This event is called when a service is unregistered. + *

    + * Warning: The order in which register and unregister events are called + * should not be relied upon. + */ +public class ServiceUnregisterEvent extends ServiceEvent { + private static final HandlerList handlers = new HandlerList(); + + public ServiceUnregisterEvent(RegisteredServiceProvider serviceProvider) { + super(serviceProvider); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/server/TabCompleteEvent.java b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java new file mode 100644 index 00000000..d4bdb9c2 --- /dev/null +++ b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.server; + +import java.util.List; +import org.apache.commons.lang3.Validate; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called when a {@link CommandSender} of any description (ie: player or + * console) attempts to tab complete. + */ +public class TabCompleteEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final CommandSender sender; + private final String buffer; + private List completions; + private boolean cancelled; + + public TabCompleteEvent(CommandSender sender, String buffer, List completions) { + Validate.notNull(sender, "sender"); + Validate.notNull(buffer, "buffer"); + Validate.notNull(completions, "completions"); + + this.sender = sender; + this.buffer = buffer; + this.completions = completions; + } + + /** + * Get the sender completing this command. + * + * @return the {@link CommandSender} instance + */ + public CommandSender getSender() { + return sender; + } + + /** + * Return the entire buffer which formed the basis of this completion. + * + * @return command buffer, as entered + */ + public String getBuffer() { + return buffer; + } + + /** + * The list of completions which will be offered to the sender, in order. + * This list is mutable and reflects what will be offered. + * + * @return a list of offered completions + */ + public List getCompletions() { + return completions; + } + + /** + * Set the completions offered, overriding any already set. + * + * @param completions the new completions + */ + public void setCompletions(List completions) { + Validate.notNull(completions); + this.completions = completions; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java new file mode 100644 index 00000000..b643b575 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.block.Block; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle collides with a block. + */ +public class VehicleBlockCollisionEvent extends VehicleCollisionEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block block; + + public VehicleBlockCollisionEvent(final Vehicle vehicle, final Block block) { + super(vehicle); + this.block = block; + } + + /** + * Gets the block the vehicle collided with + * + * @return the block the vehicle collided with + */ + public Block getBlock() { + return block; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java new file mode 100644 index 00000000..9dd05792 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java @@ -0,0 +1,12 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; + +/** + * Raised when a vehicle collides. + */ +public abstract class VehicleCollisionEvent extends VehicleEvent { + public VehicleCollisionEvent(final Vehicle vehicle) { + super(vehicle); + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java new file mode 100644 index 00000000..eb55c1f1 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle is created. + */ +public class VehicleCreateEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public VehicleCreateEvent(final Vehicle vehicle) { + super(vehicle); + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java new file mode 100644 index 00000000..dec18b83 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java @@ -0,0 +1,66 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle receives damage. + */ +public class VehicleDamageEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity attacker; + private double damage; + private boolean cancelled; + + public VehicleDamageEvent(final Vehicle vehicle, final Entity attacker, final double damage) { + super(vehicle); + this.attacker = attacker; + this.damage = damage; + } + + /** + * Gets the Entity that is attacking the vehicle + * + * @return the Entity that is attacking the vehicle + */ + public Entity getAttacker() { + return attacker; + } + + /** + * Gets the damage done to the vehicle + * + * @return the damage done to the vehicle + */ + public double getDamage() { + return damage; + } + + /** + * Sets the damage done to the vehicle + * + * @param damage The damage + */ + public void setDamage(double damage) { + this.damage = damage; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java new file mode 100644 index 00000000..f1176fd2 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle is destroyed, which could be caused by either a + * player or the environment. This is not raised if the boat is simply + * 'removed' due to other means. + */ +public class VehicleDestroyEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity attacker; + private boolean cancelled; + + public VehicleDestroyEvent(final Vehicle vehicle, final Entity attacker) { + super(vehicle); + this.attacker = attacker; + } + + /** + * Gets the Entity that has destroyed the vehicle, potentially null + * + * @return the Entity that has destroyed the vehicle, potentially null + */ + public Entity getAttacker() { + return attacker; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java new file mode 100644 index 00000000..85c9b210 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when an entity enters a vehicle. + */ +public class VehicleEnterEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity entered; + + public VehicleEnterEvent(final Vehicle vehicle, final Entity entered) { + super(vehicle); + this.entered = entered; + } + + /** + * Gets the Entity that entered the vehicle. + * + * @return the Entity that entered the vehicle + */ + public Entity getEntered() { + return entered; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java new file mode 100644 index 00000000..4d4d0e25 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle collides with an entity. + */ +public class VehicleEntityCollisionEvent extends VehicleCollisionEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity entity; + private boolean cancelled = false; + private boolean cancelledPickup = false; + private boolean cancelledCollision = false; + + public VehicleEntityCollisionEvent(final Vehicle vehicle, final Entity entity) { + super(vehicle); + this.entity = entity; + } + + public Entity getEntity() { + return entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public boolean isPickupCancelled() { + return cancelledPickup; + } + + public void setPickupCancelled(boolean cancel) { + cancelledPickup = cancel; + } + + public boolean isCollisionCancelled() { + return cancelledCollision; + } + + public void setCollisionCancelled(boolean cancel) { + cancelledCollision = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java new file mode 100644 index 00000000..b8255c01 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Event; + +/** + * Represents a vehicle-related event. + */ +public abstract class VehicleEvent extends Event { + protected Vehicle vehicle; + + public VehicleEvent(final Vehicle vehicle) { + this.vehicle = vehicle; + } + + /** + * Get the vehicle. + * + * @return the vehicle + */ + public final Vehicle getVehicle() { + return vehicle; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java new file mode 100644 index 00000000..364451b5 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Raised when a living entity exits a vehicle. + */ +public class VehicleExitEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final LivingEntity exited; + + public VehicleExitEvent(final Vehicle vehicle, final LivingEntity exited) { + super(vehicle); + this.exited = exited; + } + + /** + * Get the living entity that exited the vehicle. + * + * @return The entity. + */ + public LivingEntity getExited() { + return exited; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java new file mode 100644 index 00000000..9a13e296 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.Location; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; + +/** + * Raised when a vehicle moves. + */ +public class VehicleMoveEvent extends VehicleEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location from; + private final Location to; + + public VehicleMoveEvent(final Vehicle vehicle, final Location from, final Location to) { + super(vehicle); + + this.from = from; + this.to = to; + } + + /** + * Get the previous position. + * + * @return Old position. + */ + public Location getFrom() { + return from; + } + + /** + * Get the next position. + * + * @return New position. + */ + public Location getTo() { + return to; + } + + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java b/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java new file mode 100644 index 00000000..eebfdb15 --- /dev/null +++ b/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; + +/** + * Called when a vehicle updates + */ +public class VehicleUpdateEvent extends VehicleEvent { + private static final HandlerList handlers = new HandlerList(); + + public VehicleUpdateEvent(final Vehicle vehicle) { + super(vehicle); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java new file mode 100644 index 00000000..66fd7636 --- /dev/null +++ b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Stores data for lightning striking + */ +public class LightningStrikeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final LightningStrike bolt; + + public LightningStrikeEvent(final World world, final LightningStrike bolt) { + super(world); + this.bolt = bolt; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the bolt which is striking the earth. + * + * @return lightning entity + */ + public LightningStrike getLightning() { + return bolt; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java b/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java new file mode 100644 index 00000000..5e3716e9 --- /dev/null +++ b/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Stores data for thunder state changing in a world + */ +public class ThunderChangeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final boolean to; + + public ThunderChangeEvent(final World world, final boolean to) { + super(world); + this.to = to; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the state of thunder that the world is being set to + * + * @return true if the weather is being set to thundering, false otherwise + */ + public boolean toThunderState() { + return to; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java b/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java new file mode 100644 index 00000000..5d1234e5 --- /dev/null +++ b/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Stores data for weather changing in a world + */ +public class WeatherChangeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final boolean to; + + public WeatherChangeEvent(final World world, final boolean to) { + super(world); + this.to = to; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the state of weather that the world is being set to + * + * @return true if the weather is being set to raining, false otherwise + */ + public boolean toWeatherState() { + return to; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/weather/WeatherEvent.java b/src/main/java/org/bukkit/event/weather/WeatherEvent.java new file mode 100644 index 00000000..0cae9bcb --- /dev/null +++ b/src/main/java/org/bukkit/event/weather/WeatherEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Event; + +/** + * Represents a Weather-related event + */ +public abstract class WeatherEvent extends Event { + protected World world; + + public WeatherEvent(final World where) { + world = where; + } + + /** + * Returns the World where this event is occurring + * + * @return World this event is occurring in + */ + public final World getWorld() { + return world; + } +} diff --git a/src/main/java/org/bukkit/event/world/ChunkEvent.java b/src/main/java/org/bukkit/event/world/ChunkEvent.java new file mode 100644 index 00000000..4710d40f --- /dev/null +++ b/src/main/java/org/bukkit/event/world/ChunkEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; + +/** + * Represents a Chunk related event + */ +public abstract class ChunkEvent extends WorldEvent { + protected Chunk chunk; + + protected ChunkEvent(final Chunk chunk) { + super(chunk.getWorld()); + this.chunk = chunk; + } + + /** + * Gets the chunk being loaded/unloaded + * + * @return Chunk that triggered this event + */ + public Chunk getChunk() { + return chunk; + } +} diff --git a/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java b/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java new file mode 100644 index 00000000..a45b1cd4 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.HandlerList; + +/** + * Called when a chunk is loaded + */ +public class ChunkLoadEvent extends ChunkEvent { + private static final HandlerList handlers = new HandlerList(); + private final boolean newChunk; + + public ChunkLoadEvent(final Chunk chunk, final boolean newChunk) { + super(chunk); + this.newChunk = newChunk; + } + + /** + * Gets if this chunk was newly created or not. + *

    + * Note that if this chunk is new, it will not be populated at this time. + * + * @return true if the chunk is new, otherwise false + */ + public boolean isNewChunk() { + return newChunk; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java b/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java new file mode 100644 index 00000000..705d9556 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java @@ -0,0 +1,28 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.HandlerList; +import org.bukkit.generator.BlockPopulator; + +/** + * Thrown when a new chunk has finished being populated. + *

    + * If your intent is to populate the chunk using this event, please see {@link + * BlockPopulator} + */ +public class ChunkPopulateEvent extends ChunkEvent { + private static final HandlerList handlers = new HandlerList(); + + public ChunkPopulateEvent(final Chunk chunk) { + super(chunk); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java b/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java new file mode 100644 index 00000000..aa8573dc --- /dev/null +++ b/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java @@ -0,0 +1,58 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a chunk is unloaded + */ +public class ChunkUnloadEvent extends ChunkEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private boolean saveChunk; + + public ChunkUnloadEvent(final Chunk chunk) { + this(chunk, true); + } + + public ChunkUnloadEvent(Chunk chunk, boolean save) { + super(chunk); + this.saveChunk = save; + } + + /** + * Return whether this chunk will be saved to disk. + * + * @return chunk save status + */ + public boolean isSaveChunk() { + return saveChunk; + } + + /** + * Set whether this chunk will be saved to disk. + * + * @param saveChunk chunk save status + */ + public void setSaveChunk(boolean saveChunk) { + this.saveChunk = saveChunk; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/PortalCreateEvent.java b/src/main/java/org/bukkit/event/world/PortalCreateEvent.java new file mode 100644 index 00000000..d83d7a99 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/PortalCreateEvent.java @@ -0,0 +1,77 @@ +package org.bukkit.event.world; + +import org.bukkit.block.Block; +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Called when a portal is created + */ +public class PortalCreateEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final ArrayList blocks = new ArrayList(); + private CreateReason reason = CreateReason.FIRE; + + public PortalCreateEvent(final Collection blocks, final World world, CreateReason reason) { + super(world); + + this.blocks.addAll(blocks); + this.reason = reason; + } + + /** + * Gets an array list of all the blocks associated with the created portal + * + * @return array list of all the blocks associated with the created portal + */ + public ArrayList getBlocks() { + return this.blocks; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the reason for the portal's creation + * + * @return CreateReason for the portal's creation + */ + public CreateReason getReason() { + return reason; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the various reasons for a portal's creation + */ + public enum CreateReason { + /** + * When a portal is created 'traditionally' due to a portal frame + * being set on fire. + */ + FIRE, + /** + * When a portal is created as a destination for an existing portal + * when using the custom PortalTravelAgent + */ + OBC_DESTINATION + } +} diff --git a/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java b/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java new file mode 100644 index 00000000..e99c3c01 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.event.HandlerList; + +/** + * An event that is called when a world's spawn changes. The world's previous + * spawn location is included. + */ +public class SpawnChangeEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location previousLocation; + + public SpawnChangeEvent(final World world, final Location previousLocation) { + super(world); + this.previousLocation = previousLocation; + } + + /** + * Gets the previous spawn location + * + * @return Location that used to be spawn + */ + public Location getPreviousLocation() { + return previousLocation; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/StructureGrowEvent.java b/src/main/java/org/bukkit/event/world/StructureGrowEvent.java new file mode 100644 index 00000000..62d300d7 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/StructureGrowEvent.java @@ -0,0 +1,96 @@ +package org.bukkit.event.world; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.TreeType; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Event that is called when an organic structure attempts to grow (Sapling {@literal ->} + * Tree), (Mushroom {@literal ->} Huge Mushroom), naturally or using bonemeal. + */ +public class StructureGrowEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + private final Location location; + private final TreeType species; + private final boolean bonemeal; + private final Player player; + private final List blocks; + + public StructureGrowEvent(final Location location, final TreeType species, final boolean bonemeal, final Player player, final List blocks) { + super(location.getWorld()); + this.location = location; + this.species = species; + this.bonemeal = bonemeal; + this.player = player; + this.blocks = blocks; + } + + /** + * Gets the location of the structure. + * + * @return Location of the structure + */ + public Location getLocation() { + return location; + } + + /** + * Gets the species type (birch, normal, pine, red mushroom, brown + * mushroom) + * + * @return Structure species + */ + public TreeType getSpecies() { + return species; + } + + /** + * Checks if structure was grown using bonemeal. + * + * @return True if the structure was grown using bonemeal. + */ + public boolean isFromBonemeal() { + return bonemeal; + } + + /** + * Gets the player that created the structure. + * + * @return Player that created the structure, null if was not created + * manually + */ + public Player getPlayer() { + return player; + } + + /** + * Gets an ArrayList of all blocks associated with the structure. + * + * @return ArrayList of all blocks associated with the structure. + */ + public List getBlocks() { + return blocks; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/WorldEvent.java b/src/main/java/org/bukkit/event/world/WorldEvent.java new file mode 100644 index 00000000..bd89b81d --- /dev/null +++ b/src/main/java/org/bukkit/event/world/WorldEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.Event; + +/** + * Represents events within a world + */ +public abstract class WorldEvent extends Event { + private final World world; + + public WorldEvent(final World world) { + this.world = world; + } + + /** + * Gets the world primarily involved with this event + * + * @return World which caused this event + */ + public World getWorld() { + return world; + } +} diff --git a/src/main/java/org/bukkit/event/world/WorldInitEvent.java b/src/main/java/org/bukkit/event/world/WorldInitEvent.java new file mode 100644 index 00000000..6bf13e06 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/WorldInitEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; + +/** + * Called when a World is initializing + */ +public class WorldInitEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldInitEvent(final World world) { + super(world); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/WorldLoadEvent.java b/src/main/java/org/bukkit/event/world/WorldLoadEvent.java new file mode 100644 index 00000000..c5545aa1 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/WorldLoadEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; + +/** + * Called when a World is loaded + */ +public class WorldLoadEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldLoadEvent(final World world) { + super(world); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/WorldSaveEvent.java b/src/main/java/org/bukkit/event/world/WorldSaveEvent.java new file mode 100644 index 00000000..d46b413c --- /dev/null +++ b/src/main/java/org/bukkit/event/world/WorldSaveEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; + +/** + * Called when a World is saved. + */ +public class WorldSaveEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldSaveEvent(final World world) { + super(world); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java b/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java new file mode 100644 index 00000000..110544b7 --- /dev/null +++ b/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +/** + * Called when a World is unloaded + */ +public class WorldUnloadEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean isCancelled; + + public WorldUnloadEvent(final World world) { + super(world); + } + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/generator/BlockPopulator.java b/src/main/java/org/bukkit/generator/BlockPopulator.java new file mode 100644 index 00000000..6a70bdb8 --- /dev/null +++ b/src/main/java/org/bukkit/generator/BlockPopulator.java @@ -0,0 +1,29 @@ +package org.bukkit.generator; + +import java.util.Random; +import org.bukkit.Chunk; +import org.bukkit.World; + +/** + * A block populator is responsible for generating a small area of blocks. + *

    + * For example, generating glowstone inside the nether or generating dungeons + * full of treasure + */ +public abstract class BlockPopulator { + + /** + * Populates an area of blocks at or around the given chunk. + *

    + * The chunks on each side of the specified chunk must already exist; that + * is, there must be one north, east, south and west of the specified + * chunk. The "corner" chunks may not exist, in which scenario the + * populator should record any changes required for those chunks and + * perform the changes when they are ready. + * + * @param world The world to generate in + * @param random The random generator to use + * @param source The chunk to generate for + */ + public abstract void populate(World world, Random random, Chunk source); +} diff --git a/src/main/java/org/bukkit/generator/ChunkGenerator.java b/src/main/java/org/bukkit/generator/ChunkGenerator.java new file mode 100644 index 00000000..a8025f96 --- /dev/null +++ b/src/main/java/org/bukkit/generator/ChunkGenerator.java @@ -0,0 +1,495 @@ +package org.bukkit.generator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.material.MaterialData; + +/** + * A chunk generator is responsible for the initial shaping of an entire + * chunk. For example, the nether chunk generator should shape netherrack and + * soulsand + */ +public abstract class ChunkGenerator { + + /** + * Interface to biome section for chunk to be generated: initialized with + * default values for world type and seed. + *

    + * Custom generator is free to access and tailor values during + * generateBlockSections() or generateExtBlockSections(). + */ + public interface BiomeGrid { + + /** + * Get biome at x, z within chunk being generated + * + * @param x - 0-15 + * @param z - 0-15 + * @return Biome value + */ + Biome getBiome(int x, int z); + + /** + * Set biome at x, z within chunk being generated + * + * @param x - 0-15 + * @param z - 0-15 + * @param bio - Biome value + */ + void setBiome(int x, int z, Biome bio); + } + @Deprecated + /** + * Shapes the chunk for the given coordinates. + *

    + * This method should return a byte[32768] in the following format: + *

    +     * for (int x = 0; x < 16; x++) {
    +     *     for (int z = 0; z < 16; z++) {
    +     *         for (int y = 0; y < 128; y++) {
    +     *             // result[(x * 16 + z) * 128 + y] = ??;
    +     *         }
    +     *     }
    +     * }
    +     * 
    + *

    + * Note that this method should never attempt to get the Chunk at + * the passed coordinates, as doing so may cause an infinite loop + *

    + * Note this deprecated method will only be called when both + * generateExtBlockSections() and generateBlockSections() are + * unimplemented and return null. + * + * @param world The world this chunk will be used for + * @param random The random generator to use + * @param x The X-coordinate of the chunk + * @param z The Z-coordinate of the chunk + * @return byte[] containing the types for each block created by this + * generator + */ + public byte[] generate(World world, Random random, int x, int z) { + throw new UnsupportedOperationException("Custom generator is missing required methods: generate(), generateBlockSections() and generateExtBlockSections()"); + } + + /** + * Shapes the chunk for the given coordinates, with extended block IDs + * supported (0-4095). + *

    + * As of 1.2, chunks are represented by a vertical array of chunk + * sections, each of which is 16 x 16 x 16 blocks. If a section is empty + * (all zero), the section does not need to be supplied, reducing memory + * usage. + *

    + * This method must return a short[][] array in the following format: + *

    +     *     short[][] result = new short[world-height / 16][];
    +     * 
    + * Each section {@code (sectionID = (Y>>4))} that has blocks needs to be allocated + * space for the 4096 blocks in that section: + *
    +     *     result[sectionID] = new short[4096];
    +     * 
    + * while sections that are not populated can be left null. + *

    + * Setting a block at X, Y, Z within the chunk can be done with the + * following mapping function: + *

    +     *    void setBlock(short[][] result, int x, int y, int z, short blkid) {
    +     *        {@code if (result[y >> 4] == null) {}
    +     *            {@code result[y >> 4] = new short[4096];}
    +     *        }
    +     *        {@code result[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = blkid;}
    +     *    }
    +     * 
    + * while reading a block ID can be done with the following mapping + * function: + *
    +     *    short getBlock(short[][] result, int x, int y, int z) {
    +     *        {@code if (result[y >> 4] == null) {}
    +     *            return (short)0;
    +     *        }
    +     *        {@code return result[y >> 4][((y & 0xF) << 8) | (z << 4) | x];}
    +     *    }
    +     * 
    + * while sections that are not populated can be left null. + *

    + * Setting a block at X, Y, Z within the chunk can be done with the + * following mapping function: + *

    +     *    void setBlock(short[][] result, int x, int y, int z, short blkid) {
    +     *        {@code if (result[y >> 4) == null) {}
    +     *            {@code result[y >> 4] = new short[4096];}
    +     *        }
    +     *        {@code result[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = blkid;}
    +     *    }
    +     * 
    + * while reading a block ID can be done with the following mapping + * function: + *
    +     *    short getBlock(short[][] result, int x, int y, int z) {
    +     *        {@code if (result[y >> 4) == null) {}
    +     *            return (short)0;
    +     *        }
    +     *        {@code return result[y >> 4][((y & 0xF) << 8) | (z << 4) | x];}
    +     *    }
    +     * 
    + *

    + * Note that this method should never attempt to get the Chunk at + * the passed coordinates, as doing so may cause an infinite loop + *

    + * Note generators that do not return block IDs above 255 should not + * implement this method, or should have it return null (which will result + * in the generateBlockSections() method being called). + * + * @param world The world this chunk will be used for + * @param random The random generator to use + * @param x The X-coordinate of the chunk + * @param z The Z-coordinate of the chunk + * @param biomes Proposed biome values for chunk - can be updated by + * generator + * @return short[][] containing the types for each block created by this + * generator + * @deprecated Magic value + */ + @Deprecated + public short[][] generateExtBlockSections(World world, Random random, int x, int z, BiomeGrid biomes) { + return null; // Default - returns null, which drives call to generateBlockSections() + } + + /** + * Shapes the chunk for the given coordinates. + *

    + * As of 1.2, chunks are represented by a vertical array of chunk + * sections, each of which is 16 x 16 x 16 blocks. If a section is empty + * (all zero), the section does not need to be supplied, reducing memory + * usage. + *

    + * This method must return a byte[][] array in the following format: + *

    +     *     byte[][] result = new byte[world-height / 16][];
    +     * 
    + * Each section {@code (sectionID = (Y>>4))} that has blocks needs to be allocated + * space for the 4096 blocks in that section: + *
    +     *     result[sectionID] = new byte[4096];
    +     * 
    + * while sections that are not populated can be left null. + *

    + * Setting a block at X, Y, Z within the chunk can be done with the + * following mapping function: + *

    +     *    void setBlock(byte[][] result, int x, int y, int z, byte blkid) {
    +     *        {@code if (result[y >> 4) == null) {}
    +     *            {@code result[y >> 4] = new byte[4096];}
    +     *        }
    +     *        {@code result[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = blkid;}
    +     *    }
    +     * 
    + * while reading a block ID can be done with the following mapping + * function: + *
    +     *    byte getBlock(byte[][] result, int x, int y, int z) {
    +     *        {@code if (result[y >> 4) == null) {}
    +     *            return (byte)0;
    +     *        }
    +     *        {@code return result[y >> 4][((y & 0xF) << 8) | (z << 4) | x];}
    +     *    }
    +     * 
    + * + * Note that this method should never attempt to get the Chunk at + * the passed coordinates, as doing so may cause an infinite loop + * + * @param world The world this chunk will be used for + * @param random The random generator to use + * @param x The X-coordinate of the chunk + * @param z The Z-coordinate of the chunk + * @param biomes Proposed biome values for chunk - can be updated by + * generator + * @return short[][] containing the types for each block created by this + * generator + * @deprecated Magic value + */ + @Deprecated + public byte[][] generateBlockSections(World world, Random random, int x, int z, BiomeGrid biomes) { + return null; // Default - returns null, which drives call to generate() + } + + /** + * Shapes the chunk for the given coordinates. + * + * This method must return a ChunkData. + *

    + * Notes: + *

    + * This method should never attempt to get the Chunk at + * the passed coordinates, as doing so may cause an infinite loop + *

    + * This method should never modify a ChunkData after it has + * been returned. + *

    + * This method must return a ChunkData returned by {@link ChunkGenerator#createChunkData(World)} + * + * @param world The world this chunk will be used for + * @param random The random generator to use + * @param x The X-coordinate of the chunk + * @param z The Z-coordinate of the chunk + * @param biome Proposed biome values for chunk - can be updated by + * generator + * @return ChunkData containing the types for each block created by this + * generator + */ + public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) { + return null; // Default - returns null, which drives call to generateExtBlockSections() + } + + /** + * Create a ChunkData for a world. + * @param world the world the ChunkData is for + * @return a new ChunkData for world + */ + protected final ChunkData createChunkData(World world) { + return Bukkit.getServer().createChunkData(world); + } + + /** + * Tests if the specified location is valid for a natural spawn position + * + * @param world The world we're testing on + * @param x X-coordinate of the block to test + * @param z Z-coordinate of the block to test + * @return true if the location is valid, otherwise false + */ + public boolean canSpawn(World world, int x, int z) { + Block highest = world.getBlockAt(x, world.getHighestBlockYAt(x, z), z); + + switch (world.getEnvironment()) { + case NETHER: + return true; + case THE_END: + return highest.getType() != Material.AIR && highest.getType() != Material.WATER && highest.getType() != Material.LAVA; + case NORMAL: + default: + return highest.getType() == Material.SAND || highest.getType() == Material.GRAVEL; + } + } + + /** + * Gets a list of default {@link BlockPopulator}s to apply to a given + * world + * + * @param world World to apply to + * @return List containing any amount of BlockPopulators + */ + public List getDefaultPopulators(World world) { + return new ArrayList(); + } + + /** + * Gets a fixed spawn location to use for a given world. + *

    + * A null value is returned if a world should not use a fixed spawn point, + * and will instead attempt to find one randomly. + * + * @param world The world to locate a spawn point for + * @param random Random generator to use in the calculation + * @return Location containing a new spawn point, otherwise null + */ + public Location getFixedSpawnLocation(World world, Random random) { + return null; + } + + /** + * Data for a Chunk. + */ + public static interface ChunkData { + /** + * Get the maximum height for the chunk. + * + * Setting blocks at or above this height will do nothing. + * + * @return the maximum height + */ + public int getMaxHeight(); + + /** + * Set the block at x,y,z in the chunk data to material. + * + * Note: setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param material the type to set the block to + */ + public void setBlock(int x, int y, int z, Material material); + + /** + * Set the block at x,y,z in the chunk data to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param material the type to set the block to + */ + public void setBlock(int x, int y, int z, MaterialData material); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param material the type to set the blocks to + */ + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param material the type to set the blocks to + */ + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material); + + /** + * Get the type of the block at x, y, z. + * + * Getting blocks outside the chunk's bounds returns air. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the type of the block or Material.AIR if x, y or z are outside the chunk's bounds + */ + public Material getType(int x, int y, int z); + + /** + * Get the type and data of the block at x, y ,z. + * + * Getting blocks outside the chunk's bounds returns air. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the type and data of the block or the MaterialData for air if x, y or z are outside the chunk's bounds + */ + public MaterialData getTypeAndData(int x, int y, int z); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to block id. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param blockId the block id to set the blocks to + * @deprecated Uses magic values. + */ + @Deprecated + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, int blockId); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to block id and data. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param blockId the block id to set the blocks to + * @param data the block data to set the blocks to + * @deprecated Uses magic values. + */ + @Deprecated + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, int blockId, int data); + + /** + * Set the block at x,y,z in the chunk data to blockId. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param blockId the blockId to set the block to + * @deprecated Uses magic values + */ + @Deprecated + public void setBlock(int x, int y, int z, int blockId); + + /** + * Set the block at x,y,z in the chunk data to blockId. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param blockId the blockId to set the block to + * @param data the block data to set the block to + * @deprecated Uses magic values + */ + @Deprecated + public void setBlock(int x, int y, int z, int blockId, byte data); + + /** + * Get the blockId at x,y,z in the chunk data. + * + * Getting blocks outside the chunk's bounds returns 0. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the block id or 0 if x, y or z are outside the chunk's bounds + * @deprecated Uses magic values + */ + @Deprecated + public int getTypeId(int x, int y, int z); + + /** + * Get the block data at x,y,z in the chunk data. + * + * Getting blocks outside the chunk's bounds returns 0. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the block data value or air if x, y or z are outside the chunk's bounds + * @deprecated Uses magic values + */ + @Deprecated + public byte getData(int x, int y, int z); + } +} diff --git a/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java b/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java new file mode 100644 index 00000000..f990bb32 --- /dev/null +++ b/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java @@ -0,0 +1,77 @@ +package org.bukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.command.ConsoleCommandSender; + +/** + * Lacking an alternative, the help system will create instances of + * GenericCommandHelpTopic for each command in the server's CommandMap. You + * can use this class as a base class for custom help topics, or as an example + * for how to write your own. + */ +public class GenericCommandHelpTopic extends HelpTopic { + + protected Command command; + + public GenericCommandHelpTopic(Command command) { + this.command = command; + + if (command.getLabel().startsWith("/")) { + name = command.getLabel(); + } else { + name = "/" + command.getLabel(); + } + + // The short text is the first line of the description + int i = command.getDescription().indexOf('\n'); + if (i > 1) { + shortText = command.getDescription().substring(0, i - 1); + } else { + shortText = command.getDescription(); + } + + // Build full text + StringBuilder sb = new StringBuilder(); + + sb.append(ChatColor.GOLD); + sb.append("Description: "); + sb.append(ChatColor.WHITE); + sb.append(command.getDescription()); + + sb.append("\n"); + + sb.append(ChatColor.GOLD); + sb.append("Usage: "); + sb.append(ChatColor.WHITE); + sb.append(command.getUsage().replace("", name.substring(1))); + + if (command.getAliases().size() > 0) { + sb.append("\n"); + sb.append(ChatColor.GOLD); + sb.append("Aliases: "); + sb.append(ChatColor.WHITE); + sb.append(ChatColor.WHITE + StringUtils.join(command.getAliases(), ", ")); + } + fullText = sb.toString(); + } + + public boolean canSee(CommandSender sender) { + if (!command.isRegistered()) { + // Unregistered commands should not show up in the help + return false; + } + + if (sender instanceof ConsoleCommandSender) { + return true; + } + + if (amendedPermission != null) { + return sender.hasPermission(amendedPermission); + } else { + return command.testPermissionSilent(sender); + } + } +} diff --git a/src/main/java/org/bukkit/help/HelpMap.java b/src/main/java/org/bukkit/help/HelpMap.java new file mode 100644 index 00000000..43017c84 --- /dev/null +++ b/src/main/java/org/bukkit/help/HelpMap.java @@ -0,0 +1,79 @@ +package org.bukkit.help; + +import java.util.Collection; +import java.util.List; + +/** + * The HelpMap tracks all help topics registered in a Bukkit server. When the + * server starts up or is reloaded, help is processed and topics are added in + * the following order: + * + *

      + *
    1. General topics are loaded from the help.yml + *
    2. Plugins load and optionally call {@code addTopic()} + *
    3. Registered plugin commands are processed by {@link HelpTopicFactory} + * objects to create topics + *
    4. Topic contents are amended as directed in help.yml + *
    + */ +public interface HelpMap { + /** + * Returns a help topic for a given topic name. + * + * @param topicName The help topic name to look up. + * @return A {@link HelpTopic} object matching the topic name or null if + * none can be found. + */ + public HelpTopic getHelpTopic(String topicName); + + /** + * Returns a collection of all the registered help topics. + * + * @return All the registered help topics. + */ + public Collection getHelpTopics(); + + /** + * Adds a topic to the server's help index. + * + * @param topic The new help topic to add. + */ + public void addTopic(HelpTopic topic); + + /** + * Clears out the contents of the help index. Normally called during + * server reload. + */ + public void clear(); + + /** + * Associates a {@link HelpTopicFactory} object with given command base + * class. Plugins typically call this method during {@code onLoad()}. Once + * registered, the custom HelpTopicFactory will be used to create a custom + * {@link HelpTopic} for all commands deriving from the {@code + * commandClass} base class, or all commands deriving from {@link + * org.bukkit.command.PluginCommand} who's executor derives from {@code + * commandClass} base class. + * + * @param commandClass The class for which the custom HelpTopicFactory + * applies. Must derive from either {@link org.bukkit.command.Command} + * or {@link org.bukkit.command.CommandExecutor}. + * @param factory The {@link HelpTopicFactory} implementation to associate + * with the {@code commandClass}. + * @throws IllegalArgumentException Thrown if {@code commandClass} does + * not derive from a legal base class. + */ + public void registerHelpTopicFactory(Class commandClass, HelpTopicFactory factory); + + /** + * Gets the list of plugins the server administrator has chosen to exclude + * from the help index. Plugin authors who choose to directly extend + * {@link org.bukkit.command.Command} instead of {@link + * org.bukkit.command.PluginCommand} will need to check this collection in + * their {@link HelpTopicFactory} implementations to ensure they meet the + * server administrator's expectations. + * + * @return A list of plugins that should be excluded from the help index. + */ + public List getIgnoredPlugins(); +} diff --git a/src/main/java/org/bukkit/help/HelpTopic.java b/src/main/java/org/bukkit/help/HelpTopic.java new file mode 100644 index 00000000..7900a1b2 --- /dev/null +++ b/src/main/java/org/bukkit/help/HelpTopic.java @@ -0,0 +1,121 @@ +package org.bukkit.help; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * HelpTopic implementations are displayed to the user when the user uses the + * /help command. + *

    + * Custom implementations of this class can work at two levels. A simple + * implementation only needs to set the value of {@code name}, {@code + * shortText}, and {@code fullText} in the constructor. This base class will + * take care of the rest. + *

    + * Complex implementations can be created by overriding the behavior of all + * the methods in this class. + */ +public abstract class HelpTopic { + protected String name; + protected String shortText; + protected String fullText; + protected String amendedPermission; + + /** + * Determines if a {@link Player} is allowed to see this help topic. + *

    + * HelpTopic implementations should take server administrator wishes into + * account as set by the {@link HelpTopic#amendCanSee(String)} function. + * + * @param player The Player in question. + * @return True of the Player can see this help topic, false otherwise. + */ + public abstract boolean canSee(CommandSender player); + + /** + * Allows the server administrator to override the permission required to + * see a help topic. + *

    + * HelpTopic implementations should take this into account when + * determining topic visibility on the {@link + * HelpTopic#canSee(CommandSender)} function. + * + * @param amendedPermission The permission node the server administrator + * wishes to apply to this topic. + */ + public void amendCanSee(String amendedPermission) { + this.amendedPermission = amendedPermission; + } + + /** + * Returns the name of this help topic. + * + * @return The topic name. + */ + public String getName() { + return name; + } + + /** + * Returns a brief description that will be displayed in the topic index. + * + * @return A brief topic description. + */ + public String getShortText() { + return shortText; + } + + /** + * Returns the full description of this help topic that is displayed when + * the user requests this topic's details. + *

    + * The result will be paginated to properly fit the user's client. + * + * @param forWho The player or console requesting the full text. Useful + * for further security trimming the command's full text based on + * sub-permissions in custom implementations. + * + * @return A full topic description. + */ + public String getFullText(CommandSender forWho) { + return fullText; + } + + /** + * Allows the server admin (or another plugin) to add or replace the + * contents of a help topic. + *

    + * A null in either parameter will leave that part of the topic unchanged. + * In either amending parameter, the string {@literal } is replaced + * with the existing contents in the help topic. Use this to append or + * prepend additional content into an automatically generated help topic. + * + * @param amendedShortText The new topic short text to use, or null to + * leave alone. + * @param amendedFullText The new topic full text to use, or null to leave + * alone. + */ + public void amendTopic(String amendedShortText, String amendedFullText) { + shortText = applyAmendment(shortText, amendedShortText); + fullText = applyAmendment(fullText, amendedFullText); + } + + /** + * Developers implementing their own custom HelpTopic implementations can + * use this utility method to ensure their implementations comply with the + * expected behavior of the {@link HelpTopic#amendTopic(String, String)} + * method. + * + * @param baseText The existing text of the help topic. + * @param amendment The amending text from the amendTopic() method. + * @return The application of the amending text to the existing text, + * according to the expected rules of amendTopic(). + */ + protected String applyAmendment(String baseText, String amendment) { + if (amendment == null) { + return baseText; + } else { + return amendment.replaceAll("", baseText); + } + } +} diff --git a/src/main/java/org/bukkit/help/HelpTopicComparator.java b/src/main/java/org/bukkit/help/HelpTopicComparator.java new file mode 100644 index 00000000..3e62e91b --- /dev/null +++ b/src/main/java/org/bukkit/help/HelpTopicComparator.java @@ -0,0 +1,46 @@ +package org.bukkit.help; + +import java.util.Comparator; + +/** + * Used to impose a custom total ordering on help topics. + *

    + * All topics are listed in alphabetic order, but topics that start with a + * slash come after topics that don't. + */ +public class HelpTopicComparator implements Comparator { + + // Singleton implementations + private static final TopicNameComparator tnc = new TopicNameComparator(); + public static TopicNameComparator topicNameComparatorInstance() { + return tnc; + } + + private static final HelpTopicComparator htc = new HelpTopicComparator(); + public static HelpTopicComparator helpTopicComparatorInstance() { + return htc; + } + + private HelpTopicComparator() {} + + public int compare(HelpTopic lhs, HelpTopic rhs) { + return tnc.compare(lhs.getName(), rhs.getName()); + } + + public static class TopicNameComparator implements Comparator { + private TopicNameComparator(){} + + public int compare(String lhs, String rhs) { + boolean lhsStartSlash = lhs.startsWith("/"); + boolean rhsStartSlash = rhs.startsWith("/"); + + if (lhsStartSlash && !rhsStartSlash) { + return 1; + } else if (!lhsStartSlash && rhsStartSlash) { + return -1; + } else { + return lhs.compareToIgnoreCase(rhs); + } + } + } +} diff --git a/src/main/java/org/bukkit/help/HelpTopicFactory.java b/src/main/java/org/bukkit/help/HelpTopicFactory.java new file mode 100644 index 00000000..87d36977 --- /dev/null +++ b/src/main/java/org/bukkit/help/HelpTopicFactory.java @@ -0,0 +1,42 @@ +package org.bukkit.help; + +import org.bukkit.command.Command; + +/** + * A HelpTopicFactory is used to create custom {@link HelpTopic} objects from + * commands that inherit from a common base class or have executors that + * inherit from a common base class. You can use a custom HelpTopic to change + * the way all the commands in your plugin display in the help. If your plugin + * implements a complex permissions system, a custom help topic may also be + * appropriate. + *

    + * To automatically bind your plugin's commands to your custom HelpTopic + * implementation, first make sure all your commands or executors derive from + * a custom base class (it doesn't have to do anything). Next implement a + * custom HelpTopicFactory that accepts your custom command base class and + * instantiates an instance of your custom HelpTopic from it. Finally, + * register your HelpTopicFactory against your command base class using the + * {@link HelpMap#registerHelpTopicFactory(Class, HelpTopicFactory)} method. + *

    + * As the help system iterates over all registered commands to make help + * topics, it first checks to see if there is a HelpTopicFactory registered + * for the command's base class. If so, the factory is used to make a help + * topic rather than a generic help topic. If no factory is found for the + * command's base class and the command derives from {@link + * org.bukkit.command.PluginCommand}, then the type of the command's executor + * is inspected looking for a registered HelpTopicFactory. Finally, if no + * factory is found, a generic help topic is created for the command. + * + * @param The base class for your custom commands. + */ +public interface HelpTopicFactory { + /** + * This method accepts a command deriving from a custom command base class + * and constructs a custom HelpTopic for it. + * + * @param command The custom command to build a help topic for. + * @return A new custom help topic or {@code null} to intentionally NOT + * create a topic. + */ + public HelpTopic createTopic(TCommand command); +} diff --git a/src/main/java/org/bukkit/help/IndexHelpTopic.java b/src/main/java/org/bukkit/help/IndexHelpTopic.java new file mode 100644 index 00000000..c474031b --- /dev/null +++ b/src/main/java/org/bukkit/help/IndexHelpTopic.java @@ -0,0 +1,112 @@ +package org.bukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.util.ChatPaginator; + +import java.util.Collection; + +/** + * This help topic generates a list of other help topics. This class is useful + * for adding your own index help topics. To enforce a particular order, use a + * sorted collection. + *

    + * If a preamble is provided to the constructor, that text will be displayed + * before the first item in the index. + */ +public class IndexHelpTopic extends HelpTopic { + + protected String permission; + protected String preamble; + protected Collection allTopics; + + public IndexHelpTopic(String name, String shortText, String permission, Collection topics) { + this(name, shortText, permission, topics, null); + } + + public IndexHelpTopic(String name, String shortText, String permission, Collection topics, String preamble) { + this.name = name; + this.shortText = shortText; + this.permission = permission; + this.preamble = preamble; + setTopicsCollection(topics); + } + + /** + * Sets the contents of the internal allTopics collection. + * + * @param topics The topics to set. + */ + protected void setTopicsCollection(Collection topics) { + this.allTopics = topics; + } + + public boolean canSee(CommandSender sender) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + if (permission == null) { + return true; + } + return sender.hasPermission(permission); + } + + @Override + public void amendCanSee(String amendedPermission) { + permission = amendedPermission; + } + + public String getFullText(CommandSender sender) { + StringBuilder sb = new StringBuilder(); + + if (preamble != null) { + sb.append(buildPreamble(sender)); + sb.append("\n"); + } + + for (HelpTopic topic : allTopics) { + if (topic.canSee(sender)) { + String lineStr = buildIndexLine(sender, topic).replace("\n", ". "); + if (sender instanceof Player && lineStr.length() > ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH) { + sb.append(lineStr.substring(0, ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - 3)); + sb.append("..."); + } else { + sb.append(lineStr); + } + sb.append("\n"); + } + } + return sb.toString(); + } + + /** + * Builds the topic preamble. Override this method to change how the index + * preamble looks. + * + * @param sender The command sender requesting the preamble. + * @return The topic preamble. + */ + protected String buildPreamble(CommandSender sender) { + return ChatColor.GRAY + preamble; + } + + /** + * Builds individual lines in the index topic. Override this method to + * change how index lines are rendered. + * + * @param sender The command sender requesting the index line. + * @param topic The topic to render into an index line. + * @return The rendered index line. + */ + protected String buildIndexLine(CommandSender sender, HelpTopic topic) { + StringBuilder line = new StringBuilder(); + line.append(ChatColor.GOLD); + line.append(topic.getName()); + line.append(": "); + line.append(ChatColor.WHITE); + line.append(topic.getShortText()); + return line.toString(); + } +} diff --git a/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java b/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java new file mode 100644 index 00000000..4c16e34a --- /dev/null +++ b/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java @@ -0,0 +1,23 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.AbstractHorse; + +/** + * An interface to the inventory of an {@link AbstractHorse}. + */ +public interface AbstractHorseInventory extends Inventory { + + /** + * Gets the item in the horse's saddle slot. + * + * @return the saddle item + */ + ItemStack getSaddle(); + + /** + * Sets the item in the horse's saddle slot. + * + * @param stack the new item + */ + void setSaddle(ItemStack stack); +} diff --git a/src/main/java/org/bukkit/inventory/AnvilInventory.java b/src/main/java/org/bukkit/inventory/AnvilInventory.java new file mode 100644 index 00000000..289a630e --- /dev/null +++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java @@ -0,0 +1,29 @@ +package org.bukkit.inventory; + +/** + * Interface to the inventory of an Anvil. + */ +public interface AnvilInventory extends Inventory { + + /** + * Get the name to be applied to the repaired item. An empty string denotes + * the default item name. + * + * @return the rename text + */ + String getRenameText(); + + /** + * Get the experience cost (in levels) to complete the current repair. + * + * @return the experience cost + */ + int getRepairCost(); + + /** + * Set the experience cost (in levels) to complete the current repair. + * + * @param levels the experience cost + */ + void setRepairCost(int levels); +} diff --git a/src/main/java/org/bukkit/inventory/BeaconInventory.java b/src/main/java/org/bukkit/inventory/BeaconInventory.java new file mode 100644 index 00000000..2f8769ed --- /dev/null +++ b/src/main/java/org/bukkit/inventory/BeaconInventory.java @@ -0,0 +1,21 @@ +package org.bukkit.inventory; + +/** + * Interface to the inventory of a Beacon. + */ +public interface BeaconInventory extends Inventory { + + /** + * Set the item powering the beacon. + * + * @param item The new item + */ + void setItem(ItemStack item); + + /** + * Get the item powering the beacon. + * + * @return The current item. + */ + ItemStack getItem(); +} diff --git a/src/main/java/org/bukkit/inventory/BrewerInventory.java b/src/main/java/org/bukkit/inventory/BrewerInventory.java new file mode 100644 index 00000000..3aaf17e1 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/BrewerInventory.java @@ -0,0 +1,41 @@ +package org.bukkit.inventory; + +import org.bukkit.Material; +import org.bukkit.block.BrewingStand; + +/** + * Interface to the inventory of a Brewing Stand. + */ +public interface BrewerInventory extends Inventory { + + /** + * Get the current ingredient for brewing. + * + * @return The ingredient. + */ + ItemStack getIngredient(); + + /** + * Set the current ingredient for brewing. + * + * @param ingredient The ingredient + */ + void setIngredient(ItemStack ingredient); + + /** + * Get the current fuel for brewing. + * + * @return The fuel + */ + ItemStack getFuel(); + + /** + * Set the current fuel for brewing. Generally only + * {@link Material#BLAZE_POWDER} will be of use. + * + * @param fuel The fuel + */ + void setFuel(ItemStack fuel); + + BrewingStand getHolder(); +} diff --git a/src/main/java/org/bukkit/inventory/CraftingInventory.java b/src/main/java/org/bukkit/inventory/CraftingInventory.java new file mode 100644 index 00000000..106367e7 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/CraftingInventory.java @@ -0,0 +1,45 @@ +package org.bukkit.inventory; + +/** + * Interface to the crafting inventories + */ +public interface CraftingInventory extends Inventory { + + /** + * Check what item is in the result slot of this crafting inventory. + * + * @return The result item. + */ + ItemStack getResult(); + + /** + * Get the contents of the crafting matrix. + * + * @return The contents. + */ + ItemStack[] getMatrix(); + + /** + * Set the item in the result slot of the crafting inventory. + * + * @param newResult The new result item. + */ + void setResult(ItemStack newResult); + + /** + * Replace the contents of the crafting matrix + * + * @param contents The new contents. + * @throws IllegalArgumentException if the length of contents is greater + * than the size of the crafting matrix. + */ + void setMatrix(ItemStack[] contents); + + /** + * Get the current recipe formed on the crafting inventory, if any. + * + * @return The recipe, or null if the current contents don't match any + * recipe. + */ + Recipe getRecipe(); +} diff --git a/src/main/java/org/bukkit/inventory/DoubleChestInventory.java b/src/main/java/org/bukkit/inventory/DoubleChestInventory.java new file mode 100644 index 00000000..c03ad535 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/DoubleChestInventory.java @@ -0,0 +1,25 @@ +package org.bukkit.inventory; + +import org.bukkit.block.DoubleChest; + +/** + * Interface to the inventory of a Double Chest. + */ +public interface DoubleChestInventory extends Inventory { + + /** + * Get the left half of this double chest. + * + * @return The left side inventory + */ + Inventory getLeftSide(); + + /** + * Get the right side of this double chest. + * + * @return The right side inventory + */ + Inventory getRightSide(); + + DoubleChest getHolder(); +} diff --git a/src/main/java/org/bukkit/inventory/EnchantingInventory.java b/src/main/java/org/bukkit/inventory/EnchantingInventory.java new file mode 100644 index 00000000..6551a16a --- /dev/null +++ b/src/main/java/org/bukkit/inventory/EnchantingInventory.java @@ -0,0 +1,35 @@ +package org.bukkit.inventory; + +/** + * Interface to the inventory of an Enchantment Table. + */ +public interface EnchantingInventory extends Inventory { + + /** + * Set the item being enchanted. + * + * @param item The new item + */ + void setItem(ItemStack item); + + /** + * Get the item being enchanted. + * + * @return The current item. + */ + ItemStack getItem(); + + /** + * Set the secondary item being used for the enchant. + * + * @param item The new item + */ + void setSecondary(ItemStack item); + + /** + * Get the secondary item being used for the enchant. + * + * @return The second item + */ + ItemStack getSecondary(); +} diff --git a/src/main/java/org/bukkit/inventory/EntityEquipment.java b/src/main/java/org/bukkit/inventory/EntityEquipment.java new file mode 100644 index 00000000..aee8163c --- /dev/null +++ b/src/main/java/org/bukkit/inventory/EntityEquipment.java @@ -0,0 +1,323 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.Entity; + +/** + * An interface to a creatures inventory + */ +public interface EntityEquipment { + + /** + * Gets a copy of the item the entity is currently holding + * in their main hand. + * + * @return the currently held item + */ + ItemStack getItemInMainHand(); + + /** + * Sets the item the entity is holding in their main hand. + * + * @param item The item to put into the entities hand + */ + void setItemInMainHand(ItemStack item); + + /** + * Gets a copy of the item the entity is currently holding + * in their off hand. + * + * @return the currently held item + */ + ItemStack getItemInOffHand(); + + /** + * Sets the item the entity is holding in their off hand. + * + * @param item The item to put into the entities hand + */ + void setItemInOffHand(ItemStack item); + + /** + * Gets a copy of the item the entity is currently holding + * + * @deprecated entities can duel wield now use the methods for the + * specific hand instead + * @see #getItemInMainHand() + * @see #getItemInOffHand() + * @return the currently held item + */ + @Deprecated + ItemStack getItemInHand(); + + /** + * Sets the item the entity is holding + * + * @deprecated entities can duel wield now use the methods for the + * specific hand instead + * @see #setItemInMainHand(ItemStack) + * @see #setItemInOffHand(ItemStack) + * @param stack The item to put into the entities hand + */ + @Deprecated + void setItemInHand(ItemStack stack); + + /** + * Gets a copy of the helmet currently being worn by the entity + * + * @return The helmet being worn + */ + ItemStack getHelmet(); + + /** + * Sets the helmet worn by the entity + * + * @param helmet The helmet to put on the entity + */ + void setHelmet(ItemStack helmet); + + /** + * Gets a copy of the chest plate currently being worn by the entity + * + * @return The chest plate being worn + */ + ItemStack getChestplate(); + + /** + * Sets the chest plate worn by the entity + * + * @param chestplate The chest plate to put on the entity + */ + void setChestplate(ItemStack chestplate); + + /** + * Gets a copy of the leggings currently being worn by the entity + * + * @return The leggings being worn + */ + ItemStack getLeggings(); + + /** + * Sets the leggings worn by the entity + * + * @param leggings The leggings to put on the entity + */ + void setLeggings(ItemStack leggings); + + /** + * Gets a copy of the boots currently being worn by the entity + * + * @return The boots being worn + */ + ItemStack getBoots(); + + /** + * Sets the boots worn by the entity + * + * @param boots The boots to put on the entity + */ + void setBoots(ItemStack boots); + + /** + * Gets a copy of all worn armor + * + * @return The array of worn armor + */ + ItemStack[] getArmorContents(); + + /** + * Sets the entities armor to the provided array of ItemStacks + * + * @param items The items to set the armor as + */ + void setArmorContents(ItemStack[] items); + + /** + * Clears the entity of all armor and held items + */ + void clear(); + + /** + * @deprecated entities can duel wield now use the methods for the specific + * hand instead + * @see #getItemInMainHandDropChance() + * @see #getItemInOffHandDropChance() + * @return drop chance + */ + @Deprecated + float getItemInHandDropChance(); + + /** + * @deprecated entities can duel wield now use the methods for the specific + * hand instead + * @see #setItemInMainHandDropChance(float) + * @see #setItemInOffHandDropChance(float) + * @param chance drop chance + */ + @Deprecated + void setItemInHandDropChance(float chance); + + /** + * Gets the chance of the main hand item being dropped upon this creature's + * death. + * + *

      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return chance of the currently held item being dropped (1 for players) + */ + float getItemInMainHandDropChance(); + + /** + * Sets the chance of the item this creature is currently holding in their + * main hand being dropped upon this creature's death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance the chance of the main hand item being dropped + * @throws UnsupportedOperationException when called on players + */ + void setItemInMainHandDropChance(float chance); + + /** + * Gets the chance of the off hand item being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return chance of the off hand item being dropped (1 for players) + */ + float getItemInOffHandDropChance(); + + /** + * Sets the chance of the off hand item being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance the chance of off hand item being dropped + * @throws UnsupportedOperationException when called on players + */ + void setItemInOffHandDropChance(float chance); + + /** + * Gets the chance of the helmet being dropped upon this creature's death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return the chance of the helmet being dropped (1 for players) + */ + float getHelmetDropChance(); + + /** + * Sets the chance of the helmet being dropped upon this creature's death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance of the helmet being dropped + * @throws UnsupportedOperationException when called on players + */ + void setHelmetDropChance(float chance); + + /** + * Gets the chance of the chest plate being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return the chance of the chest plate being dropped (1 for players) + */ + float getChestplateDropChance(); + + /** + * Sets the chance of the chest plate being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance of the chest plate being dropped + * @throws UnsupportedOperationException when called on players + */ + void setChestplateDropChance(float chance); + + /** + * Gets the chance of the leggings being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return the chance of the leggings being dropped (1 for players) + */ + float getLeggingsDropChance(); + + /** + * Sets the chance of the leggings being dropped upon this creature's + * death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance chance of the leggings being dropped + * @throws UnsupportedOperationException when called on players + */ + void setLeggingsDropChance(float chance); + + /** + * Gets the chance of the boots being dropped upon this creature's death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @return the chance of the boots being dropped (1 for players) + */ + float getBootsDropChance(); + + /** + * Sets the chance of the boots being dropped upon this creature's death. + * + *
      + *
    • A drop chance of 0.0F will never drop + *
    • A drop chance of 1.0F will always drop + *
    + * + * @param chance of the boots being dropped + * @throws UnsupportedOperationException when called on players + */ + void setBootsDropChance(float chance); + + /** + * Get the entity this EntityEquipment belongs to + * + * @return the entity this EntityEquipment belongs to + */ + Entity getHolder(); +} diff --git a/src/main/java/org/bukkit/inventory/EquipmentSlot.java b/src/main/java/org/bukkit/inventory/EquipmentSlot.java new file mode 100644 index 00000000..1e7d7711 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/EquipmentSlot.java @@ -0,0 +1,11 @@ +package org.bukkit.inventory; + +public enum EquipmentSlot { + + HAND, + OFF_HAND, + FEET, + LEGS, + CHEST, + HEAD +} diff --git a/src/main/java/org/bukkit/inventory/FurnaceInventory.java b/src/main/java/org/bukkit/inventory/FurnaceInventory.java new file mode 100644 index 00000000..93b41d36 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/FurnaceInventory.java @@ -0,0 +1,53 @@ +package org.bukkit.inventory; + +import org.bukkit.block.Furnace; + +/** + * Interface to the inventory of a Furnace. + */ +public interface FurnaceInventory extends Inventory { + + /** + * Get the current item in the result slot. + * + * @return The item + */ + ItemStack getResult(); + + /** + * Get the current fuel. + * + * @return The item + */ + ItemStack getFuel(); + + /** + * Get the item currently smelting. + * + * @return The item + */ + ItemStack getSmelting(); + + /** + * Set the current fuel. + * + * @param stack The item + */ + void setFuel(ItemStack stack); + + /** + * Set the current item in the result slot. + * + * @param stack The item + */ + void setResult(ItemStack stack); + + /** + * Set the item currently smelting. + * + * @param stack The item + */ + void setSmelting(ItemStack stack); + + Furnace getHolder(); +} diff --git a/src/main/java/org/bukkit/inventory/FurnaceRecipe.java b/src/main/java/org/bukkit/inventory/FurnaceRecipe.java new file mode 100644 index 00000000..b1774d6d --- /dev/null +++ b/src/main/java/org/bukkit/inventory/FurnaceRecipe.java @@ -0,0 +1,146 @@ +package org.bukkit.inventory; + +import org.bukkit.Material; +import org.bukkit.material.MaterialData; + +/** + * Represents a smelting recipe. + */ +public class FurnaceRecipe implements Recipe { + private ItemStack output; + private ItemStack ingredient; + private float experience; + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param result The item you want the recipe to create. + * @param source The input material. + */ + public FurnaceRecipe(ItemStack result, Material source) { + this(result, source, 0, 0); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param result The item you want the recipe to create. + * @param source The input material. + */ + public FurnaceRecipe(ItemStack result, MaterialData source) { + this(result, source.getItemType(), source.getData(), 0); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param result The item you want the recipe to create. + * @param source The input material. + * @param experience The experience given by this recipe + */ + public FurnaceRecipe(ItemStack result, MaterialData source, float experience) { + this(result, source.getItemType(), source.getData(), experience); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param result The item you want the recipe to create. + * @param source The input material. + * @param data The data value. (Note: This is currently ignored by the + * CraftBukkit server.) + * @deprecated Magic value + */ + @Deprecated + public FurnaceRecipe(ItemStack result, Material source, int data) { + this(result, source, data, 0); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param result The item you want the recipe to create. + * @param source The input material. + * @param data The data value. (Note: This is currently ignored by the + * CraftBukkit server.) + * @param experience The experience given by this recipe + * @deprecated Magic value + */ + @Deprecated + public FurnaceRecipe(ItemStack result, Material source, int data, float experience) { + this.output = new ItemStack(result); + this.ingredient = new ItemStack(source, 1, (short) data); + this.experience = experience; + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @return The changed recipe, so you can chain calls. + */ + public FurnaceRecipe setInput(MaterialData input) { + return setInput(input.getItemType(), input.getData()); + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @return The changed recipe, so you can chain calls. + */ + public FurnaceRecipe setInput(Material input) { + return setInput(input, 0); + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @param data The data value. (Note: This is currently ignored by the + * CraftBukkit server.) + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + public FurnaceRecipe setInput(Material input, int data) { + this.ingredient = new ItemStack(input, 1, (short) data); + return this; + } + + /** + * Get the input material. + * + * @return The input material. + */ + public ItemStack getInput() { + return this.ingredient.clone(); + } + + /** + * Get the result of this recipe. + * + * @return The resulting stack. + */ + public ItemStack getResult() { + return output.clone(); + } + + /** + * Sets the experience given by this recipe. + * + * @param experience the experience level + */ + public void setExperience(float experience) { + this.experience = experience; + } + + /** + * Get the experience given by this recipe. + * + * @return experience level + */ + public float getExperience() { + return experience; + } +} diff --git a/src/main/java/org/bukkit/inventory/HorseInventory.java b/src/main/java/org/bukkit/inventory/HorseInventory.java new file mode 100644 index 00000000..a73c9850 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/HorseInventory.java @@ -0,0 +1,21 @@ +package org.bukkit.inventory; + +/** + * An interface to the inventory of a Horse. + */ +public interface HorseInventory extends AbstractHorseInventory { + + /** + * Gets the item in the horse's armor slot. + * + * @return the armor item + */ + ItemStack getArmor(); + + /** + * Sets the item in the horse's armor slot. + * + * @param stack the new item + */ + void setArmor(ItemStack stack); +} diff --git a/src/main/java/org/bukkit/inventory/Inventory.java b/src/main/java/org/bukkit/inventory/Inventory.java new file mode 100644 index 00000000..3275e172 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/Inventory.java @@ -0,0 +1,420 @@ +package org.bukkit.inventory; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; + +/** + * Interface to the various inventories. Behavior relating to {@link + * Material#AIR} is unspecified. + */ +public interface Inventory extends Iterable { + + /** + * Returns the size of the inventory + * + * @return The size of the inventory + */ + public int getSize(); + + /** + * Returns the maximum stack size for an ItemStack in this inventory. + * + * @return The maximum size for an ItemStack in this inventory. + */ + public int getMaxStackSize(); + + /** + * This method allows you to change the maximum stack size for an + * inventory. + *

    + * Caveats: + *

      + *
    • Not all inventories respect this value. + *
    • Stacks larger than 127 may be clipped when the world is saved. + *
    • This value is not guaranteed to be preserved; be sure to set it + * before every time you want to set a slot over the max stack size. + *
    • Stacks larger than the default max size for this type of inventory + * may not display correctly in the client. + *
    + * + * @param size The new maximum stack size for items in this inventory. + */ + public void setMaxStackSize(int size); + + /** + * Returns the name of the inventory + * + * @return The String with the name of the inventory + */ + public String getName(); + + /** + * Returns the ItemStack found in the slot at the given index + * + * @param index The index of the Slot's ItemStack to return + * @return The ItemStack in the slot + */ + public ItemStack getItem(int index); + + /** + * Stores the ItemStack at the given index of the inventory. + * + * @param index The index where to put the ItemStack + * @param item The ItemStack to set + */ + public void setItem(int index, ItemStack item); + + /** + * Stores the given ItemStacks in the inventory. This will try to fill + * existing stacks and empty slots as well as it can. + *

    + * The returned HashMap contains what it couldn't store, where the key is + * the index of the parameter, and the value is the ItemStack at that + * index of the varargs parameter. If all items are stored, it will return + * an empty HashMap. + *

    + * If you pass in ItemStacks which exceed the maximum stack size for the + * Material, first they will be added to partial stacks where + * Material.getMaxStackSize() is not exceeded, up to + * Material.getMaxStackSize(). When there are no partial stacks left + * stacks will be split on Inventory.getMaxStackSize() allowing you to + * exceed the maximum stack size for that material. + *

    + * It is known that in some implementations this method will also set + * the inputted argument amount to the number of that item not placed in + * slots. + * + * @param items The ItemStacks to add + * @return A HashMap containing items that didn't fit. + * @throws IllegalArgumentException if items or any element in it is null + */ + public HashMap addItem(ItemStack... items) throws IllegalArgumentException; + + /** + * Removes the given ItemStacks from the inventory. + *

    + * It will try to remove 'as much as possible' from the types and amounts + * you give as arguments. + *

    + * The returned HashMap contains what it couldn't remove, where the key is + * the index of the parameter, and the value is the ItemStack at that + * index of the varargs parameter. If all the given ItemStacks are + * removed, it will return an empty HashMap. + *

    + * It is known that in some implementations this method will also set the + * inputted argument amount to the number of that item not removed from + * slots. + * + * @param items The ItemStacks to remove + * @return A HashMap containing items that couldn't be removed. + * @throws IllegalArgumentException if items is null + */ + public HashMap removeItem(ItemStack... items) throws IllegalArgumentException; + + /** + * Returns all ItemStacks from the inventory + * + * @return An array of ItemStacks from the inventory. + */ + public ItemStack[] getContents(); + + /** + * Completely replaces the inventory's contents. Removes all existing + * contents and replaces it with the ItemStacks given in the array. + * + * @param items A complete replacement for the contents; the length must + * be less than or equal to {@link #getSize()}. + * @throws IllegalArgumentException If the array has more items than the + * inventory. + */ + public void setContents(ItemStack[] items) throws IllegalArgumentException; + + /** + * Return the contents from the section of the inventory where items can + * reasonably be expected to be stored. In most cases this will represent + * the entire inventory, but in some cases it may exclude armor or result + * slots. + *
    + * It is these contents which will be used for add / contains / remove + * methods which look for a specific stack. + * + * @return inventory storage contents + */ + public ItemStack[] getStorageContents(); + + /** + * Put the given ItemStacks into the storage slots + * + * @param items The ItemStacks to use as storage contents + * @throws IllegalArgumentException If the array has more items than the + * inventory. + */ + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException; + + /** + * Checks if the inventory contains any ItemStacks with the given + * materialId + * + * @param materialId The materialId to check for + * @return true if an ItemStack in this inventory contains the materialId + * @deprecated Magic value + */ + @Deprecated + public boolean contains(int materialId); + + /** + * Checks if the inventory contains any ItemStacks with the given + * material. + * + * @param material The material to check for + * @return true if an ItemStack is found with the given Material + * @throws IllegalArgumentException if material is null + */ + public boolean contains(Material material) throws IllegalArgumentException; + + /** + * Checks if the inventory contains any ItemStacks matching the given + * ItemStack. + *

    + * This will only return true if both the type and the amount of the stack + * match. + * + * @param item The ItemStack to match against + * @return false if item is null, true if any exactly matching ItemStacks + * were found + */ + public boolean contains(ItemStack item); + + /** + * Checks if the inventory contains any ItemStacks with the given + * materialId, adding to at least the minimum amount specified. + * + * @param materialId The materialId to check for + * @param amount The minimum amount to look for + * @return true if this contains any matching ItemStack with the given + * materialId and amount + * @deprecated Magic value + */ + @Deprecated + public boolean contains(int materialId, int amount); + + /** + * Checks if the inventory contains any ItemStacks with the given + * material, adding to at least the minimum amount specified. + * + * @param material The material to check for + * @param amount The minimum amount + * @return true if amount is less than 1, true if enough ItemStacks were + * found to add to the given amount + * @throws IllegalArgumentException if material is null + */ + public boolean contains(Material material, int amount) throws IllegalArgumentException; + + /** + * Checks if the inventory contains at least the minimum amount specified + * of exactly matching ItemStacks. + *

    + * An ItemStack only counts if both the type and the amount of the stack + * match. + * + * @param item the ItemStack to match against + * @param amount how many identical stacks to check for + * @return false if item is null, true if amount less than 1, true if + * amount of exactly matching ItemStacks were found + * @see #containsAtLeast(ItemStack, int) + */ + public boolean contains(ItemStack item, int amount); + + /** + * Checks if the inventory contains ItemStacks matching the given + * ItemStack whose amounts sum to at least the minimum amount specified. + * + * @param item the ItemStack to match against + * @param amount the minimum amount + * @return false if item is null, true if amount less than 1, true if + * enough ItemStacks were found to add to the given amount + */ + public boolean containsAtLeast(ItemStack item, int amount); + + /** + * Returns a HashMap with all slots and ItemStacks in the inventory with + * given materialId. + *

    + * The HashMap contains entries where, the key is the slot index, and the + * value is the ItemStack in that slot. If no matching ItemStack with the + * given materialId is found, an empty map is returned. + * + * @param materialId The materialId to look for + * @return A HashMap containing the slot index, ItemStack pairs + * @deprecated Magic value + */ + @Deprecated + public HashMap all(int materialId); + + /** + * Returns a HashMap with all slots and ItemStacks in the inventory with + * the given Material. + *

    + * The HashMap contains entries where, the key is the slot index, and the + * value is the ItemStack in that slot. If no matching ItemStack with the + * given Material is found, an empty map is returned. + * + * @param material The material to look for + * @return A HashMap containing the slot index, ItemStack pairs + * @throws IllegalArgumentException if material is null + */ + public HashMap all(Material material) throws IllegalArgumentException; + + /** + * Finds all slots in the inventory containing any ItemStacks with the + * given ItemStack. This will only match slots if both the type and the + * amount of the stack match + *

    + * The HashMap contains entries where, the key is the slot index, and the + * value is the ItemStack in that slot. If no matching ItemStack with the + * given Material is found, an empty map is returned. + * + * @param item The ItemStack to match against + * @return A map from slot indexes to item at index + */ + public HashMap all(ItemStack item); + + /** + * Finds the first slot in the inventory containing an ItemStack with the + * given materialId. + * + * @param materialId The materialId to look for + * @return The slot index of the given materialId or -1 if not found + * @deprecated Magic value + */ + @Deprecated + public int first(int materialId); + + /** + * Finds the first slot in the inventory containing an ItemStack with the + * given material + * + * @param material The material to look for + * @return The slot index of the given Material or -1 if not found + * @throws IllegalArgumentException if material is null + */ + public int first(Material material) throws IllegalArgumentException; + + /** + * Returns the first slot in the inventory containing an ItemStack with + * the given stack. This will only match a slot if both the type and the + * amount of the stack match + * + * @param item The ItemStack to match against + * @return The slot index of the given ItemStack or -1 if not found + */ + public int first(ItemStack item); + + /** + * Returns the first empty Slot. + * + * @return The first empty Slot found, or -1 if no empty slots. + */ + public int firstEmpty(); + + /** + * Removes all stacks in the inventory matching the given materialId. + * + * @param materialId The material to remove + * @deprecated Magic value + */ + @Deprecated + public void remove(int materialId); + + /** + * Removes all stacks in the inventory matching the given material. + * + * @param material The material to remove + * @throws IllegalArgumentException if material is null + */ + public void remove(Material material) throws IllegalArgumentException; + + /** + * Removes all stacks in the inventory matching the given stack. + *

    + * This will only match a slot if both the type and the amount of the + * stack match + * + * @param item The ItemStack to match against + */ + public void remove(ItemStack item); + + /** + * Clears out a particular slot in the index. + * + * @param index The index to empty. + */ + public void clear(int index); + + /** + * Clears out the whole Inventory. + */ + public void clear(); + + /** + * Gets a list of players viewing the inventory. Note that a player is + * considered to be viewing their own inventory and internal crafting + * screen even when said inventory is not open. They will normally be + * considered to be viewing their inventory even when they have a + * different inventory screen open, but it's possible for customized + * inventory screens to exclude the viewer's inventory, so this should + * never be assumed to be non-empty. + * + * @return A list of HumanEntities who are viewing this Inventory. + */ + public List getViewers(); + + /** + * Returns the title of this inventory. + * + * @return A String with the title. + */ + public String getTitle(); + + /** + * Returns what type of inventory this is. + * + * @return The InventoryType representing the type of inventory. + */ + public InventoryType getType(); + + /** + * Gets the block or entity belonging to the open inventory + * + * @return The holder of the inventory; null if it has no holder. + */ + public InventoryHolder getHolder(); + + @Override + public ListIterator iterator(); + + /** + * Returns an iterator starting at the given index. If the index is + * positive, then the first call to next() will return the item at that + * index; if it is negative, the first call to previous will return the + * item at index (getSize() + index). + * + * @param index The index. + * @return An iterator. + */ + public ListIterator iterator(int index); + + /** + * Get the location of the block or entity which corresponds to this inventory. May return null if this container + * was custom created or is a virtual / subcontainer. + * + * @return location or null if not applicable. + */ + public Location getLocation(); +} diff --git a/src/main/java/org/bukkit/inventory/InventoryHolder.java b/src/main/java/org/bukkit/inventory/InventoryHolder.java new file mode 100644 index 00000000..9c06a3d7 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/InventoryHolder.java @@ -0,0 +1,11 @@ +package org.bukkit.inventory; + +public interface InventoryHolder { + + /** + * Get the object's inventory. + * + * @return The inventory. + */ + public Inventory getInventory(); +} diff --git a/src/main/java/org/bukkit/inventory/InventoryView.java b/src/main/java/org/bukkit/inventory/InventoryView.java new file mode 100644 index 00000000..647b804e --- /dev/null +++ b/src/main/java/org/bukkit/inventory/InventoryView.java @@ -0,0 +1,325 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; + +/** + * Represents a view linking two inventories and a single player (whose + * inventory may or may not be one of the two). + *

    + * Note: If you implement this interface but fail to satisfy the expected + * contracts of certain methods, there's no guarantee that the game will work + * as it should. + */ +public abstract class InventoryView { + public final static int OUTSIDE = -999; + /** + * Represents various extra properties of certain inventory windows. + */ + public enum Property { + /** + * The progress of the down-pointing arrow in a brewing inventory. + */ + BREW_TIME(0, InventoryType.BREWING), + /** + * The progress of the flame in a furnace inventory. + */ + BURN_TIME(0, InventoryType.FURNACE), + /** + * How many total ticks the current fuel should last. + */ + TICKS_FOR_CURRENT_FUEL(1, InventoryType.FURNACE), + /** + * The progress of the right-pointing arrow in a furnace inventory. + */ + COOK_TIME(2, InventoryType.FURNACE), + /** + * How many total ticks the current smelting should last. + */ + TICKS_FOR_CURRENT_SMELTING(3, InventoryType.FURNACE), + /** + * In an enchanting inventory, the top button's experience level + * value. + */ + ENCHANT_BUTTON1(0, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's experience level + * value. + */ + ENCHANT_BUTTON2(1, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's experience level + * value. + */ + ENCHANT_BUTTON3(2, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the first four bits of the player's xpSeed. + */ + ENCHANT_XP_SEED(3, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the top button's enchantment's id + */ + ENCHANT_ID1(4, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's enchantment's id + */ + ENCHANT_ID2(5, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's enchantment's id + */ + ENCHANT_ID3(6, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the top button's level value. + */ + ENCHANT_LEVEL1(7, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's level value. + */ + ENCHANT_LEVEL2(8, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's level value. + */ + ENCHANT_LEVEL3(9, InventoryType.ENCHANTING), + /** + * In an beacon inventory, the levels of the beacon + */ + LEVELS(0, InventoryType.BEACON), + /** + * In an beacon inventory, the primary potion effect + */ + PRIMARY_EFFECT(1, InventoryType.BEACON), + /** + * In an beacon inventory, the secondary potion effect + */ + SECONDARY_EFFECT(2, InventoryType.BEACON), + /** + * The repair's cost in xp levels + */ + REPAIR_COST(0, InventoryType.ANVIL); + int id; + InventoryType style; + private Property(int id, InventoryType appliesTo) { + this.id = id; + style = appliesTo; + } + + public InventoryType getType() { + return style; + } + + /** + * + * @return the id of this view + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + } + /** + * Get the upper inventory involved in this transaction. + * + * @return the inventory + */ + public abstract Inventory getTopInventory(); + + /** + * Get the lower inventory involved in this transaction. + * + * @return the inventory + */ + public abstract Inventory getBottomInventory(); + + /** + * Get the player viewing. + * + * @return the player + */ + public abstract HumanEntity getPlayer(); + + /** + * Determine the type of inventory involved in the transaction. This + * indicates the window style being shown. It will never return PLAYER, + * since that is common to all windows. + * + * @return the inventory type + */ + public abstract InventoryType getType(); + + /** + * Sets one item in this inventory view by its raw slot ID. + *

    + * Note: If slot ID -999 is chosen, it may be expected that the item is + * dropped on the ground. This is not required behaviour, however. + * + * @param slot The ID as returned by InventoryClickEvent.getRawSlot() + * @param item The new item to put in the slot, or null to clear it. + */ + public void setItem(int slot, ItemStack item) { + if (slot != OUTSIDE) { + if (slot < getTopInventory().getSize()) { + getTopInventory().setItem(convertSlot(slot), item); + } else { + getBottomInventory().setItem(convertSlot(slot), item); + } + } else { + getPlayer().getWorld().dropItemNaturally(getPlayer().getLocation(), item); + } + } + + /** + * Gets one item in this inventory view by its raw slot ID. + * + * @param slot The ID as returned by InventoryClickEvent.getRawSlot() + * @return The item currently in the slot. + */ + public ItemStack getItem(int slot) { + if (slot == OUTSIDE) { + return null; + } + if (slot < getTopInventory().getSize()) { + return getTopInventory().getItem(convertSlot(slot)); + } else { + return getBottomInventory().getItem(convertSlot(slot)); + } + } + + /** + * Sets the item on the cursor of one of the viewing players. + * + * @param item The item to put on the cursor, or null to remove the item + * on their cursor. + */ + public final void setCursor(ItemStack item) { + getPlayer().setItemOnCursor(item); + } + + /** + * Get the item on the cursor of one of the viewing players. + * + * @return The item on the player's cursor, or null if they aren't holding + * one. + */ + public final ItemStack getCursor() { + return getPlayer().getItemOnCursor(); + } + + /** + * Converts a raw slot ID into its local slot ID into whichever of the two + * inventories the slot points to. + *

    + * If the raw slot refers to the upper inventory, it will be returned + * unchanged and thus be suitable for getTopInventory().getItem(); if it + * refers to the lower inventory, the output will differ from the input + * and be suitable for getBottomInventory().getItem(). + * + * @param rawSlot The raw slot ID. + * @return The converted slot ID. + */ + public final int convertSlot(int rawSlot) { + int numInTop = getTopInventory().getSize(); + // Index from the top inventory as having slots from [0,size] + if (rawSlot < numInTop) { + return rawSlot; + } + + // Move down the slot index by the top size + int slot = rawSlot - numInTop; + + // Player crafting slots are indexed differently. The matrix is caught by the first return. + // Creative mode is the same, except that you can't see the crafting slots (but the IDs are still used) + if (getType() == InventoryType.CRAFTING || getType() == InventoryType.CREATIVE) { + /** + * Raw Slots: + * + * 5 1 2 0 + * 6 3 4 + * 7 + * 8 45 + * 9 10 11 12 13 14 15 16 17 + * 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 + * 36 37 38 39 40 41 42 43 44 + */ + + /** + * Converted Slots: + * + * 39 1 2 0 + * 38 3 4 + * 37 + * 36 40 + * 9 10 11 12 13 14 15 16 17 + * 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 + * 0 1 2 3 4 5 6 7 8 + */ + + if (slot < 4) { + // Send [5,8] to [39,36] + return 39 - slot; + } else if (slot > 39) { + // Slot lives in the extra slot section + return slot; + } else { + // Reset index so 9 -> 0 + slot -= 4; + } + } + + // 27 = 36 - 9 + if (slot >= 27) { + // Put into hotbar section + slot -= 27; + } else { + // Take out of hotbar section + // 9 = 36 - 27 + slot += 9; + } + + return slot; + } + + /** + * Closes the inventory view. + */ + public final void close() { + getPlayer().closeInventory(); + } + + /** + * Check the total number of slots in this view, combining the upper and + * lower inventories. + *

    + * Note though that it's possible for this to be greater than the sum of + * the two inventories if for example some slots are not being used. + * + * @return The total size + */ + public final int countSlots() { + return getTopInventory().getSize() + getBottomInventory().getSize(); + } + + /** + * Sets an extra property of this inventory if supported by that + * inventory, for example the state of a progress bar. + * + * @param prop the window property to update + * @param value the new value for the window property + * @return true if the property was updated successfully, false if the + * property is not supported by that inventory + */ + public final boolean setProperty(Property prop, int value) { + return getPlayer().setWindowProperty(prop, value); + } + + /** + * Get the title of this inventory window. + * + * @return The title. + */ + public final String getTitle() { + return getTopInventory().getTitle(); + } +} diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java new file mode 100644 index 00000000..52a8d4d8 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -0,0 +1,124 @@ +package org.bukkit.inventory; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +/** + * An instance of the ItemFactory can be obtained with {@link + * Server#getItemFactory()}. + *

    + * The ItemFactory is solely responsible for creating item meta containers to + * apply on item stacks. + */ +public interface ItemFactory { + + /** + * This creates a new item meta for the material. + * + * @param material The material to consider as base for the meta + * @return a new ItemMeta that could be applied to an item stack of the + * specified material + */ + ItemMeta getItemMeta(final Material material); + + /** + * This method checks the item meta to confirm that it is applicable (no + * data lost if applied) to the specified ItemStack. + *

    + * A {@link SkullMeta} would not be valid for a sword, but a normal {@link + * ItemMeta} from an enchanted dirt block would. + * + * @param meta Meta to check + * @param stack Item that meta will be applied to + * @return true if the meta can be applied without losing data, false + * otherwise + * @throws IllegalArgumentException if the meta was not created by this + * factory + */ + boolean isApplicable(final ItemMeta meta, final ItemStack stack) throws IllegalArgumentException; + + /** + * This method checks the item meta to confirm that it is applicable (no + * data lost if applied) to the specified Material. + *

    + * A {@link SkullMeta} would not be valid for a sword, but a normal {@link + * ItemMeta} from an enchanted dirt block would. + * + * @param meta Meta to check + * @param material Material that meta will be applied to + * @return true if the meta can be applied without losing data, false + * otherwise + * @throws IllegalArgumentException if the meta was not created by this + * factory + */ + boolean isApplicable(final ItemMeta meta, final Material material) throws IllegalArgumentException; + + /** + * This method is used to compare two item meta data objects. + * + * @param meta1 First meta to compare, and may be null to indicate no data + * @param meta2 Second meta to compare, and may be null to indicate no + * data + * @return false if one of the meta has data the other does not, otherwise + * true + * @throws IllegalArgumentException if either meta was not created by this + * factory + */ + boolean equals(final ItemMeta meta1, final ItemMeta meta2) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified stack. + *

    + * The item meta returned will always be a valid meta for a given + * ItemStack of the specified material. It may be a more or less specific + * meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. + *

    + * Example, if a {@link SkullMeta} is being applied to a book, this method + * would return a {@link BookMeta} containing all information in the + * specified meta that is applicable to an {@link ItemMeta}, the highest + * common interface. + * + * @param meta the meta to convert + * @param stack the stack to convert the meta for + * @return An appropriate item meta for the specified item stack. No + * guarantees are made as to if a copy is returned. This will be null + * for a stack of air. + * @throws IllegalArgumentException if the specified meta was not created + * by this factory + */ + ItemMeta asMetaFor(final ItemMeta meta, final ItemStack stack) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified material. + *

    + * The item meta returned will always be a valid meta for a given + * ItemStack of the specified material. It may be a more or less specific + * meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. + *

    + * Example, if a {@link SkullMeta} is being applied to a book, this method + * would return a {@link BookMeta} containing all information in the + * specified meta that is applicable to an {@link ItemMeta}, the highest + * common interface. + * + * @param meta the meta to convert + * @param material the material to convert the meta for + * @return An appropriate item meta for the specified item material. No + * guarantees are made as to if a copy is returned. This will be null for air. + * @throws IllegalArgumentException if the specified meta was not created + * by this factory + */ + ItemMeta asMetaFor(final ItemMeta meta, final Material material) throws IllegalArgumentException; + + /** + * Returns the default color for all leather armor. + * + * @return the default color for leather armor + */ + Color getDefaultLeatherColor(); +} diff --git a/src/main/java/org/bukkit/inventory/ItemFlag.java b/src/main/java/org/bukkit/inventory/ItemFlag.java new file mode 100644 index 00000000..2a8af7ba --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ItemFlag.java @@ -0,0 +1,32 @@ +package org.bukkit.inventory; + +/** + * A ItemFlag can hide some Attributes from ItemStacks + */ +public enum ItemFlag { + + /** + * Setting to show/hide enchants + */ + HIDE_ENCHANTS, + /** + * Setting to show/hide Attributes like Damage + */ + HIDE_ATTRIBUTES, + /** + * Setting to show/hide the unbreakable State + */ + HIDE_UNBREAKABLE, + /** + * Setting to show/hide what the ItemStack can break/destroy + */ + HIDE_DESTROYS, + /** + * Setting to show/hide where this ItemStack can be build/placed on + */ + HIDE_PLACED_ON, + /** + * Setting to show/hide potion effects on this ItemStack + */ + HIDE_POTION_EFFECTS; +} diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java new file mode 100644 index 00000000..4ec2e069 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -0,0 +1,611 @@ +package org.bukkit.inventory; + +import com.google.common.collect.ImmutableMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Utility; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; + +/** + * Represents a stack of items + */ +public class ItemStack implements Cloneable, ConfigurationSerializable { + private int type = 0; + private int amount = 0; + private MaterialData data = null; + private short durability = 0; + private ItemMeta meta; + + @Utility + protected ItemStack() {} + + /** + * Defaults stack size to 1, with no extra data + * + * @param type item material id + * @deprecated Magic value + */ + @Deprecated + public ItemStack(final int type) { + this(type, 1); + } + + /** + * Defaults stack size to 1, with no extra data + * + * @param type item material + */ + public ItemStack(final Material type) { + this(type, 1); + } + + /** + * An item stack with no extra data + * + * @param type item material id + * @param amount stack size + * @deprecated Magic value + */ + @Deprecated + public ItemStack(final int type, final int amount) { + this(type, amount, (short) 0); + } + + /** + * An item stack with no extra data + * + * @param type item material + * @param amount stack size + */ + public ItemStack(final Material type, final int amount) { + this(type.getId(), amount); + } + + /** + * An item stack with the specified damage / durability + * + * @param type item material id + * @param amount stack size + * @param damage durability / damage + * @deprecated Magic value + */ + @Deprecated + public ItemStack(final int type, final int amount, final short damage) { + this.type = type; + this.amount = amount; + this.durability = damage; + } + + /** + * An item stack with the specified damage / durability + * + * @param type item material + * @param amount stack size + * @param damage durability / damage + */ + public ItemStack(final Material type, final int amount, final short damage) { + this(type.getId(), amount, damage); + } + + /** + * @param type the raw type id + * @param amount the amount in the stack + * @param damage the damage value of the item + * @param data the data value or null + * @deprecated this method uses an ambiguous data byte object + */ + @Deprecated + public ItemStack(final int type, final int amount, final short damage, final Byte data) { + this.type = type; + this.amount = amount; + this.durability = damage; + if (data != null) { + createData(data); + this.durability = data; + } + } + + /** + * @param type the type + * @param amount the amount in the stack + * @param damage the damage value of the item + * @param data the data value or null + * @deprecated this method uses an ambiguous data byte object + */ + @Deprecated + public ItemStack(final Material type, final int amount, final short damage, final Byte data) { + this(type.getId(), amount, damage, data); + } + + /** + * Creates a new item stack derived from the specified stack + * + * @param stack the stack to copy + * @throws IllegalArgumentException if the specified stack is null or + * returns an item meta not created by the item factory + */ + public ItemStack(final ItemStack stack) throws IllegalArgumentException { + Validate.notNull(stack, "Cannot copy null stack"); + this.type = stack.getTypeId(); + this.amount = stack.getAmount(); + this.durability = stack.getDurability(); + this.data = stack.getData(); + if (stack.hasItemMeta()) { + setItemMeta0(stack.getItemMeta(), getType0()); + } + } + + /** + * Gets the type of this item + * + * @return Type of the items in this stack + */ + @Utility + public Material getType() { + return getType0(getTypeId()); + } + + private Material getType0() { + return getType0(this.type); + } + + private static Material getType0(int id) { + Material material = Material.getMaterial(id); + return material == null ? Material.AIR : material; + } + + /** + * Sets the type of this item + *

    + * Note that in doing so you will reset the MaterialData for this stack + * + * @param type New type to set the items in this stack to + */ + @Utility + public void setType(Material type) { + Validate.notNull(type, "Material cannot be null"); + setTypeId(type.getId()); + } + + /** + * Gets the type id of this item + * + * @return Type Id of the items in this stack + * @deprecated Magic value + */ + @Deprecated + public int getTypeId() { + return type; + } + + /** + * Sets the type id of this item + *

    + * Note that in doing so you will reset the MaterialData for this stack + * + * @param type New type id to set the items in this stack to + * @deprecated Magic value + */ + @Deprecated + public void setTypeId(int type) { + this.type = type; + if (this.meta != null) { + this.meta = Bukkit.getItemFactory().asMetaFor(meta, getType0()); + } + createData((byte) 0); + } + + /** + * Gets the amount of items in this stack + * + * @return Amount of items in this stack + */ + public int getAmount() { + return amount; + } + + /** + * Sets the amount of items in this stack + * + * @param amount New amount of items in this stack + */ + public void setAmount(int amount) { + this.amount = amount; + } + + /** + * Gets the MaterialData for this stack of items + * + * @return MaterialData for this item + */ + public MaterialData getData() { + Material mat = getType(); + if (data == null && mat != null && mat.getData() != null) { + data = mat.getNewData((byte) this.getDurability()); + } + + return data; + } + + /** + * Sets the MaterialData for this stack of items + * + * @param data New MaterialData for this item + */ + public void setData(MaterialData data) { + Material mat = getType(); + + if (data == null || mat == null || mat.getData() == null) { + this.data = data; + } else { + if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) { + this.data = data; + } else { + throw new IllegalArgumentException("Provided data is not of type " + mat.getData().getName() + ", found " + data.getClass().getName()); + } + } + } + + /** + * Sets the durability of this item + * + * @param durability Durability of this item + */ + public void setDurability(final short durability) { + this.durability = durability; + } + + /** + * Gets the durability of this item + * + * @return Durability of this item + */ + public short getDurability() { + return durability; + } + + /** + * Get the maximum stacksize for the material hold in this ItemStack. + * (Returns -1 if it has no idea) + * + * @return The maximum you can stack this material to. + */ + @Utility + public int getMaxStackSize() { + Material material = getType(); + if (material != null) { + return material.getMaxStackSize(); + } + return -1; + } + + private void createData(final byte data) { + Material mat = Material.getMaterial(type); + + if (mat == null) { + this.data = new MaterialData(type, data); + } else { + this.data = mat.getNewData(data); + } + } + + @Override + @Utility + public String toString() { + StringBuilder toString = new StringBuilder("ItemStack{").append(getType().name()).append(" x ").append(getAmount()); + if (hasItemMeta()) { + toString.append(", ").append(getItemMeta()); + } + return toString.append('}').toString(); + } + + @Override + @Utility + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ItemStack)) { + return false; + } + + ItemStack stack = (ItemStack) obj; + return getAmount() == stack.getAmount() && isSimilar(stack); + } + + /** + * This method is the same as equals, but does not consider stack size + * (amount). + * + * @param stack the item stack to compare to + * @return true if the two stacks are equal, ignoring the amount + */ + @Utility + public boolean isSimilar(ItemStack stack) { + if (stack == null) { + return false; + } + if (stack == this) { + return true; + } + return getTypeId() == stack.getTypeId() && getDurability() == stack.getDurability() && hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true); + } + + @Override + public ItemStack clone() { + try { + ItemStack itemStack = (ItemStack) super.clone(); + + if (this.meta != null) { + itemStack.meta = this.meta.clone(); + } + + if (this.data != null) { + itemStack.data = this.data.clone(); + } + + return itemStack; + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + @Override + @Utility + public int hashCode() { + int hash = 1; + + hash = hash * 31 + getTypeId(); + hash = hash * 31 + getAmount(); + hash = hash * 31 + (getDurability() & 0xffff); + hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0); + + return hash; + } + + /** + * Checks if this ItemStack contains the given {@link Enchantment} + * + * @param ench Enchantment to test + * @return True if this has the given enchantment + */ + public boolean containsEnchantment(Enchantment ench) { + return meta == null ? false : meta.hasEnchant(ench); + } + + /** + * Gets the level of the specified enchantment on this item stack + * + * @param ench Enchantment to check + * @return Level of the enchantment, or 0 + */ + public int getEnchantmentLevel(Enchantment ench) { + return meta == null ? 0 : meta.getEnchantLevel(ench); + } + + /** + * Gets a map containing all enchantments and their levels on this item. + * + * @return Map of enchantments. + */ + public Map getEnchantments() { + return meta == null ? ImmutableMap.of() : meta.getEnchants(); + } + + /** + * Adds the specified enchantments to this item stack. + *

    + * This method is the same as calling {@link + * #addEnchantment(Enchantment, int)} for each + * element of the map. + * + * @param enchantments Enchantments to add + * @throws IllegalArgumentException if the specified enchantments is null + * @throws IllegalArgumentException if any specific enchantment or level + * is null. Warning: Some enchantments may be added before this + * exception is thrown. + */ + @Utility + public void addEnchantments(Map enchantments) { + Validate.notNull(enchantments, "Enchantments cannot be null"); + for (Map.Entry entry : enchantments.entrySet()) { + addEnchantment(entry.getKey(), entry.getValue()); + } + } + + /** + * Adds the specified {@link Enchantment} to this item stack. + *

    + * If this item stack already contained the given enchantment (at any + * level), it will be replaced. + * + * @param ench Enchantment to add + * @param level Level of the enchantment + * @throws IllegalArgumentException if enchantment null, or enchantment is + * not applicable + */ + @Utility + public void addEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Enchantment cannot be null"); + if ((level < ench.getStartLevel()) || (level > ench.getMaxLevel())) { + throw new IllegalArgumentException("Enchantment level is either too low or too high (given " + level + ", bounds are " + ench.getStartLevel() + " to " + ench.getMaxLevel() + ")"); + } else if (!ench.canEnchantItem(this)) { + throw new IllegalArgumentException("Specified enchantment cannot be applied to this itemstack"); + } + + addUnsafeEnchantment(ench, level); + } + + /** + * Adds the specified enchantments to this item stack in an unsafe manner. + *

    + * This method is the same as calling {@link + * #addUnsafeEnchantment(Enchantment, int)} for + * each element of the map. + * + * @param enchantments Enchantments to add + */ + @Utility + public void addUnsafeEnchantments(Map enchantments) { + for (Map.Entry entry : enchantments.entrySet()) { + addUnsafeEnchantment(entry.getKey(), entry.getValue()); + } + } + + /** + * Adds the specified {@link Enchantment} to this item stack. + *

    + * If this item stack already contained the given enchantment (at any + * level), it will be replaced. + *

    + * This method is unsafe and will ignore level restrictions or item type. + * Use at your own discretion. + * + * @param ench Enchantment to add + * @param level Level of the enchantment + */ + public void addUnsafeEnchantment(Enchantment ench, int level) { + (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(getType0()) : meta).addEnchant(ench, level, true); + } + + /** + * Removes the specified {@link Enchantment} if it exists on this + * ItemStack + * + * @param ench Enchantment to remove + * @return Previous level, or 0 + */ + public int removeEnchantment(Enchantment ench) { + int level = getEnchantmentLevel(ench); + if (level == 0 || meta == null) { + return level; + } + meta.removeEnchant(ench); + return level; + } + + @Utility + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("type", getType().name()); + + if (getDurability() != 0) { + result.put("damage", getDurability()); + } + + if (getAmount() != 1) { + result.put("amount", getAmount()); + } + + ItemMeta meta = getItemMeta(); + if (!Bukkit.getItemFactory().equals(meta, null)) { + result.put("meta", meta); + } + + return result; + } + + /** + * Required method for configuration serialization + * + * @param args map to deserialize + * @return deserialized item stack + * @see ConfigurationSerializable + */ + public static ItemStack deserialize(Map args) { + Material type = Material.getMaterial((String) args.get("type")); + short damage = 0; + int amount = 1; + + if (args.containsKey("damage")) { + damage = ((Number) args.get("damage")).shortValue(); + } + + if (args.containsKey("amount")) { + amount = ((Number) args.get("amount")).intValue(); + } + + ItemStack result = new ItemStack(type, amount, damage); + + if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated + Object raw = args.get("enchantments"); + + if (raw instanceof Map) { + Map map = (Map) raw; + + for (Map.Entry entry : map.entrySet()) { + Enchantment enchantment = Enchantment.getByName(entry.getKey().toString()); + + if ((enchantment != null) && (entry.getValue() instanceof Integer)) { + result.addUnsafeEnchantment(enchantment, (Integer) entry.getValue()); + } + } + } + } else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist + Object raw = args.get("meta"); + if (raw instanceof ItemMeta) { + result.setItemMeta((ItemMeta) raw); + } + } + + return result; + } + + /** + * Get a copy of this ItemStack's {@link ItemMeta}. + * + * @return a copy of the current ItemStack's ItemData + */ + public ItemMeta getItemMeta() { + return this.meta == null ? Bukkit.getItemFactory().getItemMeta(getType0()) : this.meta.clone(); + } + + /** + * Checks to see if any meta data has been defined. + * + * @return Returns true if some meta data has been set for this item + */ + public boolean hasItemMeta() { + return !Bukkit.getItemFactory().equals(meta, null); + } + + /** + * Set the ItemMeta of this ItemStack. + * + * @param itemMeta new ItemMeta, or null to indicate meta data be cleared. + * @return True if successfully applied ItemMeta, see {@link + * ItemFactory#isApplicable(ItemMeta, ItemStack)} + * @throws IllegalArgumentException if the item meta was not created by + * the {@link ItemFactory} + */ + public boolean setItemMeta(ItemMeta itemMeta) { + return setItemMeta0(itemMeta, getType0()); + } + + /* + * Cannot be overridden, so it's safe for constructor call + */ + private boolean setItemMeta0(ItemMeta itemMeta, Material material) { + if (itemMeta == null) { + this.meta = null; + return true; + } + if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) { + return false; + } + this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material); + if (this.meta == itemMeta) { + this.meta = itemMeta.clone(); + } + + return true; + } +} diff --git a/src/main/java/org/bukkit/inventory/LlamaInventory.java b/src/main/java/org/bukkit/inventory/LlamaInventory.java new file mode 100644 index 00000000..9b3dcf3f --- /dev/null +++ b/src/main/java/org/bukkit/inventory/LlamaInventory.java @@ -0,0 +1,23 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.Llama; + +/** + * An interface to the inventory of a {@link Llama}. + */ +public interface LlamaInventory extends AbstractHorseInventory { + + /* + * Gets the item in the llama's decor slot. + * + * @return the decor item + */ + ItemStack getDecor(); + + /** + * Sets the item in the llama's decor slot. + * + * @param stack the new item + */ + void setDecor(ItemStack stack); +} diff --git a/src/main/java/org/bukkit/inventory/MainHand.java b/src/main/java/org/bukkit/inventory/MainHand.java new file mode 100644 index 00000000..75f12f15 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/MainHand.java @@ -0,0 +1,9 @@ +package org.bukkit.inventory; + +/** + * Represents the chosen main hand of a player + */ +public enum MainHand { + LEFT, + RIGHT +} diff --git a/src/main/java/org/bukkit/inventory/Merchant.java b/src/main/java/org/bukkit/inventory/Merchant.java new file mode 100644 index 00000000..c8e68570 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/Merchant.java @@ -0,0 +1,69 @@ +package org.bukkit.inventory; + +import java.util.List; + +import org.bukkit.entity.HumanEntity; + +/** + * Represents a merchant. A merchant is a special type of inventory which can + * facilitate custom trades between items. + */ +public interface Merchant { + + /** + * Get a list of trades currently available from this merchant. + * + * @return an immutable list of trades + */ + List getRecipes(); + + /** + * Set the list of trades currently available from this merchant. + *
    + * This will not change the selected trades of players currently trading + * with this merchant. + * + * @param recipes a list of recipes + */ + void setRecipes(List recipes); + + /** + * Get the recipe at a certain index of this merchant's trade list. + * + * @param i the index + * @return the recipe + * @throws IndexOutOfBoundsException + */ + MerchantRecipe getRecipe(int i) throws IndexOutOfBoundsException; + + /** + * Set the recipe at a certain index of this merchant's trade list. + * + * @param i the index + * @param recipe the recipe + * @throws IndexOutOfBoundsException + */ + void setRecipe(int i, MerchantRecipe recipe) throws IndexOutOfBoundsException; + + /** + * Get the number of trades this merchant currently has available. + * + * @return the recipe count + */ + int getRecipeCount(); + + /** + * Gets whether this merchant is currently trading. + * + * @return whether the merchant is trading + */ + boolean isTrading(); + + /** + * Gets the player this merchant is trading with, or null if it is not + * currently trading. + * + * @return the trader, or null + */ + HumanEntity getTrader(); +} diff --git a/src/main/java/org/bukkit/inventory/MerchantInventory.java b/src/main/java/org/bukkit/inventory/MerchantInventory.java new file mode 100644 index 00000000..f4eb3408 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/MerchantInventory.java @@ -0,0 +1,24 @@ +package org.bukkit.inventory; + +/** + * Represents a trading inventory between a player and a merchant. + *
    + * The holder of this Inventory is the owning Villager, or null if the player is + * trading with a merchant created by a plugin. + */ +public interface MerchantInventory extends Inventory { + + /** + * Get the index of the currently selected recipe. + * + * @return the index of the currently selected recipe + */ + int getSelectedRecipeIndex(); + + /** + * Get the currently selected recipe. + * + * @return the currently selected recipe + */ + MerchantRecipe getSelectedRecipe(); +} diff --git a/src/main/java/org/bukkit/inventory/MerchantRecipe.java b/src/main/java/org/bukkit/inventory/MerchantRecipe.java new file mode 100644 index 00000000..9a03d20e --- /dev/null +++ b/src/main/java/org/bukkit/inventory/MerchantRecipe.java @@ -0,0 +1,125 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a merchant's trade. + * + * Trades can take one or two ingredients, and provide one result. The + * ingredients' Itemstack amounts are respected in the trade. + *
    + * A trade has a limited number of uses, after which the trade can no longer be + * used, unless the player uses a different trade, which will cause its maximum + * uses to increase. + *
    + * A trade may or may not reward experience for being completed. + * + * @see org.bukkit.event.entity.VillagerReplenishTradeEvent + */ +public class MerchantRecipe implements Recipe { + + private ItemStack result; + private List ingredients = new ArrayList(); + private int uses; + private int maxUses; + private boolean experienceReward; + + public MerchantRecipe(ItemStack result, int maxUses) { + this(result, 0, maxUses, false); + } + + public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward) { + this.result = result; + this.uses = uses; + this.maxUses = maxUses; + this.experienceReward = experienceReward; + } + + @Override + public ItemStack getResult() { + return result; + } + + public void addIngredient(ItemStack item) { + Preconditions.checkState(ingredients.size() < 2, "MerchantRecipe can only have 2 ingredients"); + ingredients.add(item.clone()); + } + + public void removeIngredient(int index) { + ingredients.remove(index); + } + + public void setIngredients(List ingredients) { + this.ingredients = new ArrayList(); + for (ItemStack item : ingredients) { + this.ingredients.add(item.clone()); + } + } + + public List getIngredients() { + List copy = new ArrayList(); + for (ItemStack item : ingredients) { + copy.add(item.clone()); + } + return copy; + } + + /** + * Get the number of times this trade has been used. + * + * @return the number of uses + */ + public int getUses() { + return uses; + } + + /** + * Set the number of times this trade has been used. + * + * @param uses the number of uses + */ + public void setUses(int uses) { + this.uses = uses; + } + + /** + * Get the maximum number of uses this trade has. + *
    + * The maximum uses of this trade may increase when a player trades with the + * owning merchant. + * + * @return the maximum number of uses + */ + public int getMaxUses() { + return maxUses; + } + + /** + * Set the maximum number of uses this trade has. + * + * @param maxUses the maximum number of time this trade can be used + */ + public void setMaxUses(int maxUses) { + this.maxUses = maxUses; + } + + /** + * Whether to reward experience for the trade. + * + * @return whether to reward experience for completing this trade + */ + public boolean hasExperienceReward() { + return experienceReward; + } + + /** + * Set whether to reward experience for the trade. + * + * @param flag whether to reward experience for completing this trade + */ + public void setExperienceReward(boolean flag) { + this.experienceReward = flag; + } +} diff --git a/src/main/java/org/bukkit/inventory/PlayerInventory.java b/src/main/java/org/bukkit/inventory/PlayerInventory.java new file mode 100644 index 00000000..557cc04d --- /dev/null +++ b/src/main/java/org/bukkit/inventory/PlayerInventory.java @@ -0,0 +1,214 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.HumanEntity; + +/** + * Interface to the inventory of a Player, including the four armor slots and any extra slots. + */ +public interface PlayerInventory extends Inventory { + + /** + * Get all ItemStacks from the armor slots + * + * @return All the ItemStacks from the armor slots + */ + public ItemStack[] getArmorContents(); + + /** + * Get all additional ItemStacks stored in this inventory. + *
    + * NB: What defines an extra slot is up to the implementation, however it + * will not be contained within {@link #getStorageContents()} or + * {@link #getArmorContents()} + * + * @return All additional ItemStacks + */ + public ItemStack[] getExtraContents(); + + /** + * Return the ItemStack from the helmet slot + * + * @return The ItemStack in the helmet slot + */ + public ItemStack getHelmet(); + + /** + * Return the ItemStack from the chestplate slot + * + * @return The ItemStack in the chestplate slot + */ + public ItemStack getChestplate(); + + /** + * Return the ItemStack from the leg slot + * + * @return The ItemStack in the leg slot + */ + public ItemStack getLeggings(); + + /** + * Return the ItemStack from the boots slot + * + * @return The ItemStack in the boots slot + */ + public ItemStack getBoots(); + + /** + * Stores the ItemStack at the given index of the inventory. + *

    + * Indexes 0 through 8 refer to the hotbar. 9 through 35 refer to the main inventory, counting up from 9 at the top + * left corner of the inventory, moving to the right, and moving to the row below it back on the left side when it + * reaches the end of the row. It follows the same path in the inventory like you would read a book. + *

    + * Indexes 36 through 39 refer to the armor slots. Though you can set armor with this method using these indexes, + * you are encouraged to use the provided methods for those slots. + *

    + * If you attempt to use this method with an index less than 0 or greater than 39, an ArrayIndexOutOfBounds + * exception will be thrown. + * + * @param index The index where to put the ItemStack + * @param item The ItemStack to set + * @throws ArrayIndexOutOfBoundsException when index < 0 || index > 39 + * @see #setBoots(ItemStack) + * @see #setChestplate(ItemStack) + * @see #setHelmet(ItemStack) + * @see #setLeggings(ItemStack) + */ + @Override + public void setItem(int index, ItemStack item); + + /** + * Put the given ItemStacks into the armor slots + * + * @param items The ItemStacks to use as armour + */ + public void setArmorContents(ItemStack[] items); + + /** + * Put the given ItemStacks into the extra slots + *
    + * See {@link #getExtraContents()} for an explanation of extra slots. + * + * @param items The ItemStacks to use as extra + */ + public void setExtraContents(ItemStack[] items); + + /** + * Put the given ItemStack into the helmet slot. This does not check if + * the ItemStack is a helmet + * + * @param helmet The ItemStack to use as helmet + */ + public void setHelmet(ItemStack helmet); + + /** + * Put the given ItemStack into the chestplate slot. This does not check + * if the ItemStack is a chestplate + * + * @param chestplate The ItemStack to use as chestplate + */ + public void setChestplate(ItemStack chestplate); + + /** + * Put the given ItemStack into the leg slot. This does not check if the + * ItemStack is a pair of leggings + * + * @param leggings The ItemStack to use as leggings + */ + public void setLeggings(ItemStack leggings); + + /** + * Put the given ItemStack into the boots slot. This does not check if the + * ItemStack is a boots + * + * @param boots The ItemStack to use as boots + */ + public void setBoots(ItemStack boots); + + /** + * Gets a copy of the item the player is currently holding + * in their main hand. + * + * @return the currently held item + */ + ItemStack getItemInMainHand(); + + /** + * Sets the item the player is holding in their main hand. + * + * @param item The item to put into the player's hand + */ + void setItemInMainHand(ItemStack item); + + /** + * Gets a copy of the item the player is currently holding + * in their off hand. + * + * @return the currently held item + */ + ItemStack getItemInOffHand(); + + /** + * Sets the item the player is holding in their off hand. + * + * @param item The item to put into the player's hand + */ + void setItemInOffHand(ItemStack item); + + /** + * Gets a copy of the item the player is currently holding + * + * @deprecated players can duel wield now use the methods for the + * specific hand instead + * @see #getItemInMainHand() + * @see #getItemInOffHand() + * @return the currently held item + */ + @Deprecated + public ItemStack getItemInHand(); + + /** + * Sets the item the player is holding + * + * @deprecated players can duel wield now use the methods for the + * specific hand instead + * @see #setItemInMainHand(ItemStack) + * @see #setItemInOffHand(ItemStack) + * @param stack The item to put into the player's hand + */ + @Deprecated + public void setItemInHand(ItemStack stack); + + /** + * Get the slot number of the currently held item + * + * @return Held item slot number + */ + public int getHeldItemSlot(); + + /** + * Set the slot number of the currently held item. + *

    + * This validates whether the slot is between 0 and 8 inclusive. + * + * @param slot The new slot number + * @throws IllegalArgumentException Thrown if slot is not between 0 and 8 + * inclusive + */ + public void setHeldItemSlot(int slot); + + /** + * Clears all matching items from the inventory. Setting either value to + * -1 will skip it's check, while setting both to -1 will clear all items + * in your inventory unconditionally. + * + * @param id the id of the item you want to clear from the inventory + * @param data the data of the item you want to clear from the inventory + * @return The number of items cleared + * @deprecated Magic value + */ + @Deprecated + public int clear(int id, int data); + + public HumanEntity getHolder(); +} diff --git a/src/main/java/org/bukkit/inventory/Recipe.java b/src/main/java/org/bukkit/inventory/Recipe.java new file mode 100644 index 00000000..7977ce2d --- /dev/null +++ b/src/main/java/org/bukkit/inventory/Recipe.java @@ -0,0 +1,14 @@ +package org.bukkit.inventory; + +/** + * Represents some type of crafting recipe. + */ +public interface Recipe { + + /** + * Get the result of this recipe. + * + * @return The result stack + */ + ItemStack getResult(); +} diff --git a/src/main/java/org/bukkit/inventory/ShapedRecipe.java b/src/main/java/org/bukkit/inventory/ShapedRecipe.java new file mode 100644 index 00000000..6a470065 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ShapedRecipe.java @@ -0,0 +1,171 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.Validate; + +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.material.MaterialData; + +/** + * Represents a shaped (ie normal) crafting recipe. + */ +public class ShapedRecipe implements Recipe, Keyed { + private final NamespacedKey key; + private final ItemStack output; + private String[] rows; + private Map ingredients = new HashMap(); + + @Deprecated + public ShapedRecipe(ItemStack result) { + this.key = NamespacedKey.randomKey(); + this.output = new ItemStack(result); + } + + /** + * Create a shaped recipe to craft the specified ItemStack. The + * constructor merely determines the result and type; to set the actual + * recipe, you'll need to call the appropriate methods. + * + * @param key the unique recipe key + * @param result The item you want the recipe to create. + * @see ShapedRecipe#shape(String...) + * @see ShapedRecipe#setIngredient(char, Material) + * @see ShapedRecipe#setIngredient(char, Material, int) + * @see ShapedRecipe#setIngredient(char, MaterialData) + */ + public ShapedRecipe(NamespacedKey key, ItemStack result) { + Preconditions.checkArgument(key != null, "key"); + + this.key = key; + this.output = new ItemStack(result); + } + + /** + * Set the shape of this recipe to the specified rows. Each character + * represents a different ingredient; exactly what each character + * represents is set separately. The first row supplied corresponds with + * the upper most part of the recipe on the workbench e.g. if all three + * rows are supplies the first string represents the top row on the + * workbench. + * + * @param shape The rows of the recipe (up to 3 rows). + * @return The changed recipe, so you can chain calls. + */ + public ShapedRecipe shape(final String... shape) { + Validate.notNull(shape, "Must provide a shape"); + Validate.isTrue(shape.length > 0 && shape.length < 4, "Crafting recipes should be 1, 2, 3 rows, not ", shape.length); + + int lastLen = -1; + for (String row : shape) { + Validate.notNull(row, "Shape cannot have null rows"); + Validate.isTrue(row.length() > 0 && row.length() < 4, "Crafting rows should be 1, 2, or 3 characters, not ", row.length()); + + Validate.isTrue(lastLen == -1 || lastLen == row.length(), "Crafting recipes must be rectangular"); + lastLen = row.length(); + } + this.rows = new String[shape.length]; + for (int i = 0; i < shape.length; i++) { + this.rows[i] = shape[i]; + } + + // Remove character mappings for characters that no longer exist in the shape + HashMap newIngredients = new HashMap(); + for (String row : shape) { + for (Character c : row.toCharArray()) { + newIngredients.put(c, ingredients.get(c)); + } + } + this.ingredients = newIngredients; + + return this; + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @return The changed recipe, so you can chain calls. + */ + public ShapedRecipe setIngredient(char key, MaterialData ingredient) { + return setIngredient(key, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @return The changed recipe, so you can chain calls. + */ + public ShapedRecipe setIngredient(char key, Material ingredient) { + return setIngredient(key, ingredient, 0); + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @param raw The raw material data as an integer. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + public ShapedRecipe setIngredient(char key, Material ingredient, int raw) { + Validate.isTrue(ingredients.containsKey(key), "Symbol does not appear in the shape:", key); + + // -1 is the old wildcard, map to Short.MAX_VALUE as the new one + if (raw == -1) { + raw = Short.MAX_VALUE; + } + + ingredients.put(key, new ItemStack(ingredient, 1, (short) raw)); + return this; + } + + /** + * Get a copy of the ingredients map. + * + * @return The mapping of character to ingredients. + */ + public Map getIngredientMap() { + HashMap result = new HashMap(); + for (Map.Entry ingredient : ingredients.entrySet()) { + if (ingredient.getValue() == null) { + result.put(ingredient.getKey(), null); + } else { + result.put(ingredient.getKey(), ingredient.getValue().clone()); + } + } + return result; + } + + /** + * Get the shape. + * + * @return The recipe's shape. + */ + public String[] getShape() { + return rows.clone(); + } + + /** + * Get the result. + * + * @return The result stack. + */ + public ItemStack getResult() { + return output.clone(); + } + + @Override + public NamespacedKey getKey() { + return key; + } +} diff --git a/src/main/java/org/bukkit/inventory/ShapelessRecipe.java b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java new file mode 100644 index 00000000..5b332e84 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java @@ -0,0 +1,242 @@ +package org.bukkit.inventory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Keyed; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.material.MaterialData; + +/** + * Represents a shapeless recipe, where the arrangement of the ingredients on + * the crafting grid does not matter. + */ +public class ShapelessRecipe implements Recipe, Keyed { + private final NamespacedKey key; + private final ItemStack output; + private final List ingredients = new ArrayList(); + + @Deprecated + public ShapelessRecipe(ItemStack result) { + this.key = NamespacedKey.randomKey(); + this.output = new ItemStack(result); + } + + /** + * Create a shapeless recipe to craft the specified ItemStack. The + * constructor merely determines the result and type; to set the actual + * recipe, you'll need to call the appropriate methods. + * + * @param key the unique recipe key + * @param result The item you want the recipe to create. + * @see ShapelessRecipe#addIngredient(Material) + * @see ShapelessRecipe#addIngredient(MaterialData) + * @see ShapelessRecipe#addIngredient(Material,int) + * @see ShapelessRecipe#addIngredient(int,Material) + * @see ShapelessRecipe#addIngredient(int,MaterialData) + * @see ShapelessRecipe#addIngredient(int,Material,int) + */ + public ShapelessRecipe(NamespacedKey key, ItemStack result) { + this.key = key; + this.output = new ItemStack(result); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + public ShapelessRecipe addIngredient(MaterialData ingredient) { + return addIngredient(1, ingredient); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + public ShapelessRecipe addIngredient(Material ingredient) { + return addIngredient(1, ingredient, 0); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @param rawdata The data value, or -1 to allow any data value. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + public ShapelessRecipe addIngredient(Material ingredient, int rawdata) { + return addIngredient(1, ingredient, rawdata); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + public ShapelessRecipe addIngredient(int count, MaterialData ingredient) { + return addIngredient(count, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + public ShapelessRecipe addIngredient(int count, Material ingredient) { + return addIngredient(count, ingredient, 0); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @param rawdata The data value, or -1 to allow any data value. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + public ShapelessRecipe addIngredient(int count, Material ingredient, int rawdata) { + Validate.isTrue(ingredients.size() + count <= 9, "Shapeless recipes cannot have more than 9 ingredients"); + + // -1 is the old wildcard, map to Short.MAX_VALUE as the new one + if (rawdata == -1) { + rawdata = Short.MAX_VALUE; + } + + while (count-- > 0) { + ingredients.add(new ItemStack(ingredient, 1, (short) rawdata)); + } + return this; + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. Only removes exact matches, + * with a data value of 0. + * + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + public ShapelessRecipe removeIngredient(Material ingredient) { + return removeIngredient(ingredient, 0); + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. If the data value is -1, + * only ingredients with a -1 data value will be removed. + * + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + public ShapelessRecipe removeIngredient(MaterialData ingredient) { + return removeIngredient(ingredient.getItemType(), ingredient.getData()); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. Only removes exact + * matches, with a data value of 0. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + public ShapelessRecipe removeIngredient(int count, Material ingredient) { + return removeIngredient(count, ingredient, 0); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. If the data value + * is -1, only ingredients with a -1 data value will be removed. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove. + * @return The changed recipe. + */ + public ShapelessRecipe removeIngredient(int count, MaterialData ingredient) { + return removeIngredient(count, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. If the data value is -1, + * only ingredients with a -1 data value will be removed. + * + * @param ingredient The ingredient to remove + * @param rawdata The data value; + * @return The changed recipe. + * @deprecated Magic value + */ + @Deprecated + public ShapelessRecipe removeIngredient(Material ingredient, int rawdata) { + return removeIngredient(1, ingredient, rawdata); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. If the data value + * is -1, only ingredients with a -1 data value will be removed. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove. + * @param rawdata The data value. + * @return The changed recipe. + * @deprecated Magic value + */ + @Deprecated + public ShapelessRecipe removeIngredient(int count, Material ingredient, int rawdata) { + Iterator iterator = ingredients.iterator(); + while (count > 0 && iterator.hasNext()) { + ItemStack stack = iterator.next(); + if (stack.getType() == ingredient && stack.getDurability() == rawdata) { + iterator.remove(); + count--; + } + } + return this; + } + + /** + * Get the result of this recipe. + * + * @return The result stack. + */ + public ItemStack getResult() { + return output.clone(); + } + + /** + * Get the list of ingredients used for this recipe. + * + * @return The input list + */ + public List getIngredientList() { + ArrayList result = new ArrayList(ingredients.size()); + for (ItemStack ingredient : ingredients) { + result.add(ingredient.clone()); + } + return result; + } + + @Override + public NamespacedKey getKey() { + return key; + } +} diff --git a/src/main/java/org/bukkit/inventory/meta/BannerMeta.java b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java new file mode 100644 index 00000000..54bf9372 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java @@ -0,0 +1,80 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import org.bukkit.DyeColor; +import org.bukkit.block.banner.Pattern; + +public interface BannerMeta extends ItemMeta { + + /** + * Returns the base color for this banner + * + * @return the base color + * @deprecated banner color is now stored as the data value, not meta. + */ + @Deprecated + DyeColor getBaseColor(); + + /** + * Sets the base color for this banner + * + * @param color the base color + * @deprecated banner color is now stored as the data value, not meta. + */ + @Deprecated + void setBaseColor(DyeColor color); + + /** + * Returns a list of patterns on this banner + * + * @return the patterns + */ + List getPatterns(); + + /** + * Sets the patterns used on this banner + * + * @param patterns the new list of patterns + */ + void setPatterns(List patterns); + + /** + * Adds a new pattern on top of the existing + * patterns + * + * @param pattern the new pattern to add + */ + void addPattern(Pattern pattern); + + /** + * Returns the pattern at the specified index + * + * @param i the index + * @return the pattern + */ + Pattern getPattern(int i); + + /** + * Removes the pattern at the specified index + * + * @param i the index + * @return the removed pattern + */ + Pattern removePattern(int i); + + /** + * Sets the pattern at the specified index + * + * @param i the index + * @param pattern the new pattern + */ + void setPattern(int i, Pattern pattern); + + /** + * Returns the number of patterns on this + * banner + * + * @return the number of patterns + */ + int numberOfPatterns(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java new file mode 100644 index 00000000..25c7eba0 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java @@ -0,0 +1,35 @@ + +package org.bukkit.inventory.meta; + +import org.bukkit.block.BlockState; + +public interface BlockStateMeta extends ItemMeta { + + /** + * Returns whether the item has a block state currently + * attached to it. + * + * @return whether a block state is already attached + */ + boolean hasBlockState(); + + /** + * Returns the currently attached block state for this + * item or creates a new one if one doesn't exist. + * + * The state is a copy, it must be set back (or to another + * item) with {@link #setBlockState(BlockState)} + * + * @return the attached state or a new state + */ + BlockState getBlockState(); + + /** + * Attaches a copy of the passed block state to the item. + * + * @param blockState the block state to attach to the block. + * @throws IllegalArgumentException if the blockState is null + * or invalid for this item. + */ + void setBlockState(BlockState blockState); +} diff --git a/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/src/main/java/org/bukkit/inventory/meta/BookMeta.java new file mode 100644 index 00000000..75b03ac3 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java @@ -0,0 +1,248 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import net.md_5.bungee.api.chat.BaseComponent; + +import org.bukkit.Material; + +/** + * Represents a book ({@link Material#BOOK_AND_QUILL} or {@link + * Material#WRITTEN_BOOK}) that can have a title, an author, and pages. + */ +public interface BookMeta extends ItemMeta { + + /** + * Represents the generation (or level of copying) of a written book + */ + enum Generation { + /** + * Book written into a book-and-quill. Can be copied. (Default value) + */ + ORIGINAL, + /** + * Book that was copied from an original. Can be copied. + */ + COPY_OF_ORIGINAL, + /** + * Book that was copied from a copy of an original. Can't be copied. + */ + COPY_OF_COPY, + /** + * Unused; unobtainable by players. Can't be copied. + */ + TATTERED; + } + + /** + * Checks for the existence of a title in the book. + * + * @return true if the book has a title + */ + boolean hasTitle(); + + /** + * Gets the title of the book. + *

    + * Plugins should check that hasTitle() returns true before calling this + * method. + * + * @return the title of the book + */ + String getTitle(); + + /** + * Sets the title of the book. + *

    + * Limited to 16 characters. Removes title when given null. + * + * @param title the title to set + * @return true if the title was successfully set + */ + boolean setTitle(String title); + + /** + * Checks for the existence of an author in the book. + * + * @return true if the book has an author + */ + boolean hasAuthor(); + + /** + * Gets the author of the book. + *

    + * Plugins should check that hasAuthor() returns true before calling this + * method. + * + * @return the author of the book + */ + String getAuthor(); + + /** + * Sets the author of the book. Removes author when given null. + * + * @param author the author to set + */ + void setAuthor(String author); + + /** + * Checks for the existence of generation level in the book. + * + * @return true if the book has a generation level + */ + boolean hasGeneration(); + + /** + * Gets the generation of the book. + *

    + * Plugins should check that hasGeneration() returns true before calling + * this method. + * + * @return the generation of the book + */ + Generation getGeneration(); + + /** + * Sets the generation of the book. Removes generation when given null. + * + * @param generation the generation to set + */ + void setGeneration(Generation generation); + + /** + * Checks for the existence of pages in the book. + * + * @return true if the book has pages + */ + boolean hasPages(); + + /** + * Gets the specified page in the book. The given page must exist. + * + * @param page the page number to get + * @return the page from the book + */ + String getPage(int page); + + /** + * Sets the specified page in the book. Pages of the book must be + * contiguous. + *

    + * The data can be up to 256 characters in length, additional characters + * are truncated. + * + * @param page the page number to set + * @param data the data to set for that page + */ + void setPage(int page, String data); + + /** + * Gets all the pages in the book. + * + * @return list of all the pages in the book + */ + List getPages(); + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of pages to set the book to use + */ + void setPages(List pages); + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of strings, each being a page + */ + void setPages(String... pages); + + /** + * Adds new pages to the end of the book. Up to a maximum of 50 pages with + * 256 characters per page. + * + * @param pages A list of strings, each being a page + */ + void addPage(String... pages); + + /** + * Gets the number of pages in the book. + * + * @return the number of pages in the book + */ + int getPageCount(); + + BookMeta clone(); + + // Spigot start + public class Spigot extends ItemMeta.Spigot { + + /** + * Gets the specified page in the book. The given page must exist. + * + * @param page the page number to get + * @return the page from the book + */ + public BaseComponent[] getPage(int page) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sets the specified page in the book. Pages of the book must be + * contiguous. + *

    + * The data can be up to 256 characters in length, additional characters + * are truncated. + * + * @param page the page number to set + * @param data the data to set for that page + */ + public void setPage(int page, BaseComponent... data) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Gets all the pages in the book. + * + * @return list of all the pages in the book + */ + public List getPages() { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of pages to set the book to use + */ + public void setPages(List pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of component arrays, each being a page + */ + public void setPages(BaseComponent[]... pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Adds new pages to the end of the book. Up to a maximum of 50 pages + * with 256 characters per page. + * + * @param pages A list of component arrays, each being a page + */ + public void addPage(BaseComponent[]... pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + @Override + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java b/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java new file mode 100644 index 00000000..fb93d030 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java @@ -0,0 +1,79 @@ +package org.bukkit.inventory.meta; + +import java.util.Map; + +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; + +/** + * EnchantmentMeta is specific to items that can store enchantments, as + * opposed to being enchanted. {@link Material#ENCHANTED_BOOK} is an example + * of an item with enchantment storage. + */ +public interface EnchantmentStorageMeta extends ItemMeta { + + /** + * Checks for the existence of any stored enchantments. + * + * @return true if an enchantment exists on this meta + */ + boolean hasStoredEnchants(); + + /** + * Checks for storage of the specified enchantment. + * + * @param ench enchantment to check + * @return true if this enchantment is stored in this meta + */ + boolean hasStoredEnchant(Enchantment ench); + + /** + * Checks for the level of the stored enchantment. + * + * @param ench enchantment to check + * @return The level that the specified stored enchantment has, or 0 if + * none + */ + int getStoredEnchantLevel(Enchantment ench); + + /** + * Gets a copy the stored enchantments in this ItemMeta. + * + * @return An immutable copy of the stored enchantments + */ + Map getStoredEnchants(); + + /** + * Stores the specified enchantment in this item meta. + * + * @param ench Enchantment to store + * @param level Level for the enchantment + * @param ignoreLevelRestriction this indicates the enchantment should be + * applied, ignoring the level limit + * @return true if the item meta changed as a result of this call, false + * otherwise + * @throws IllegalArgumentException if enchantment is null + */ + boolean addStoredEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * Remove the specified stored enchantment from this item meta. + * + * @param ench Enchantment to remove + * @return true if the item meta changed as a result of this call, false + * otherwise + * @throws IllegalArgumentException if enchantment is null + */ + boolean removeStoredEnchant(Enchantment ench) throws IllegalArgumentException; + + /** + * Checks if the specified enchantment conflicts with any enchantments in + * this ItemMeta. + * + * @param ench enchantment to test + * @return true if the enchantment conflicts, false otherwise + */ + boolean hasConflictingStoredEnchant(Enchantment ench); + + EnchantmentStorageMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java b/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java new file mode 100644 index 00000000..47046f16 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java @@ -0,0 +1,34 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.FireworkEffect; +import org.bukkit.Material; + +/** + * Represents a meta that can store a single FireworkEffect. An example + * includes {@link Material#FIREWORK_CHARGE}. + */ +public interface FireworkEffectMeta extends ItemMeta { + + /** + * Sets the firework effect for this meta. + * + * @param effect the effect to set, or null to indicate none. + */ + void setEffect(FireworkEffect effect); + + /** + * Checks if this meta has an effect. + * + * @return true if this meta has an effect, false otherwise + */ + boolean hasEffect(); + + /** + * Gets the firework effect for this meta. + * + * @return the current effect, or null if none + */ + FireworkEffect getEffect(); + + FireworkEffectMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java b/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java new file mode 100644 index 00000000..d3a86c7e --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java @@ -0,0 +1,94 @@ +package org.bukkit.inventory.meta; + +import java.util.List; + +import org.bukkit.FireworkEffect; +import org.bukkit.Material; + +/** + * Represents a {@link Material#FIREWORK} and its effects. + */ +public interface FireworkMeta extends ItemMeta { + + /** + * Add another effect to this firework. + * + * @param effect The firework effect to add + * @throws IllegalArgumentException If effect is null + */ + void addEffect(FireworkEffect effect) throws IllegalArgumentException; + + /** + * Add several effects to this firework. + * + * @param effects The firework effects to add + * @throws IllegalArgumentException If effects is null + * @throws IllegalArgumentException If any effect is null (may be thrown + * after changes have occurred) + */ + void addEffects(FireworkEffect... effects) throws IllegalArgumentException; + + /** + * Add several firework effects to this firework. + * + * @param effects An iterable object whose iterator yields the desired + * firework effects + * @throws IllegalArgumentException If effects is null + * @throws IllegalArgumentException If any effect is null (may be thrown + * after changes have occurred) + */ + void addEffects(Iterable effects) throws IllegalArgumentException; + + /** + * Get the effects in this firework. + * + * @return An immutable list of the firework effects + */ + List getEffects(); + + /** + * Get the number of effects in this firework. + * + * @return The number of effects + */ + int getEffectsSize(); + + /** + * Remove an effect from this firework. + * + * @param index The index of the effect to remove + * @throws IndexOutOfBoundsException If index {@literal < 0 or index >} {@link + * #getEffectsSize()} + */ + void removeEffect(int index) throws IndexOutOfBoundsException; + + /** + * Remove all effects from this firework. + */ + void clearEffects(); + + /** + * Get whether this firework has any effects. + * + * @return true if it has effects, false if there are no effects + */ + boolean hasEffects(); + + /** + * Gets the approximate height the firework will fly. + * + * @return approximate flight height of the firework. + */ + int getPower(); + + /** + * Sets the approximate power of the firework. Each level of power is half + * a second of flight time. + * + * @param power the power of the firework, from 0-128 + * @throws IllegalArgumentException if {@literal height<0 or height>128} + */ + void setPower(int power) throws IllegalArgumentException; + + FireworkMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java new file mode 100644 index 00000000..62ddbbba --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java @@ -0,0 +1,231 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; + +/** + * This type represents the storage mechanism for auxiliary item data. + *

    + * An implementation will handle the creation and application for ItemMeta. + * This class should not be implemented by a plugin in a live environment. + */ +public interface ItemMeta extends Cloneable, ConfigurationSerializable { + + /** + * Checks for existence of a display name. + * + * @return true if this has a display name + */ + boolean hasDisplayName(); + + /** + * Gets the display name that is set. + *

    + * Plugins should check that hasDisplayName() returns true + * before calling this method. + * + * @return the display name that is set + */ + String getDisplayName(); + + /** + * Sets the display name. + * + * @param name the name to set + */ + void setDisplayName(String name); + + /** + * Checks for existence of a localized name. + * + * @return true if this has a localized name + */ + boolean hasLocalizedName(); + + /** + * Gets the localized display name that is set. + *

    + * Plugins should check that hasLocalizedName() returns true + * before calling this method. + * + * @return the localized name that is set + */ + String getLocalizedName(); + + /** + * Sets the localized name. + * + * @param name the name to set + */ + void setLocalizedName(String name); + + /** + * Checks for existence of lore. + * + * @return true if this has lore + */ + boolean hasLore(); + + /** + * Gets the lore that is set. + *

    + * Plugins should check if hasLore() returns true before + * calling this method. + * + * @return a list of lore that is set + */ + List getLore(); + + /** + * Sets the lore for this item. + * Removes lore when given null. + * + * @param lore the lore that will be set + */ + void setLore(List lore); + + /** + * Checks for the existence of any enchantments. + * + * @return true if an enchantment exists on this meta + */ + boolean hasEnchants(); + + /** + * Checks for existence of the specified enchantment. + * + * @param ench enchantment to check + * @return true if this enchantment exists for this meta + */ + boolean hasEnchant(Enchantment ench); + + /** + * Checks for the level of the specified enchantment. + * + * @param ench enchantment to check + * @return The level that the specified enchantment has, or 0 if none + */ + int getEnchantLevel(Enchantment ench); + + /** + * Returns a copy the enchantments in this ItemMeta.
    + * Returns an empty map if none. + * + * @return An immutable copy of the enchantments + */ + Map getEnchants(); + + /** + * Adds the specified enchantment to this item meta. + * + * @param ench Enchantment to add + * @param level Level for the enchantment + * @param ignoreLevelRestriction this indicates the enchantment should be + * applied, ignoring the level limit + * @return true if the item meta changed as a result of this call, false + * otherwise + */ + boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * Removes the specified enchantment from this item meta. + * + * @param ench Enchantment to remove + * @return true if the item meta changed as a result of this call, false + * otherwise + */ + boolean removeEnchant(Enchantment ench); + + /** + * Checks if the specified enchantment conflicts with any enchantments in + * this ItemMeta. + * + * @param ench enchantment to test + * @return true if the enchantment conflicts, false otherwise + */ + boolean hasConflictingEnchant(Enchantment ench); + + /** + * Set itemflags which should be ignored when rendering a ItemStack in the Client. This Method does silently ignore double set itemFlags. + * + * @param itemFlags The hideflags which shouldn't be rendered + */ + void addItemFlags(ItemFlag... itemFlags); + + /** + * Remove specific set of itemFlags. This tells the Client it should render it again. This Method does silently ignore double removed itemFlags. + * + * @param itemFlags Hideflags which should be removed + */ + void removeItemFlags(ItemFlag... itemFlags); + + /** + * Get current set itemFlags. The collection returned is unmodifiable. + * + * @return A set of all itemFlags set + */ + Set getItemFlags(); + + /** + * Check if the specified flag is present on this item. + * + * @param flag the flag to check + * @return if it is present + */ + boolean hasItemFlag(ItemFlag flag); + + /** + * Return if the unbreakable tag is true. An unbreakable item will not lose + * durability. + * + * @return true if the unbreakable tag is true + */ + boolean isUnbreakable(); + + /** + * Sets the unbreakable tag. An unbreakable item will not lose durability. + * + * @param unbreakable true if set unbreakable + */ + void setUnbreakable(boolean unbreakable); + + @SuppressWarnings("javadoc") + ItemMeta clone(); + + // Spigot start + public class Spigot + { + + /** + * Sets the unbreakable tag + * + * @param unbreakable true if set unbreakable + * @deprecated see {@link ItemMeta#setUnbreakable(boolean)} + */ + @Deprecated + public void setUnbreakable(boolean unbreakable) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Return if the unbreakable tag is true + * + * @return true if the unbreakable tag is true + * @deprecated see {@link ItemMeta#isUnbreakable()} + */ + @Deprecated + public boolean isUnbreakable() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + Spigot spigot(); + // Spigot end +} diff --git a/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java new file mode 100644 index 00000000..5cf201d6 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java @@ -0,0 +1,39 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import org.bukkit.NamespacedKey; + +public interface KnowledgeBookMeta extends ItemMeta { + + /** + * Checks for the existence of recipes in the book. + * + * @return true if the book has recipes + */ + boolean hasRecipes(); + + /** + * Gets all the recipes in the book. + * + * @return list of all the recipes in the book + */ + List getRecipes(); + + /** + * Clears the existing book recipes, and sets the book to use the provided + * recipes. + * + * @param recipes A list of recipes to set the book to use + */ + void setRecipes(List recipes); + + /** + * Adds new recipe to the end of the book. + * + * @param recipes A list of recipe keys + */ + void addRecipe(NamespacedKey... recipes); + + @Override + KnowledgeBookMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java b/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java new file mode 100644 index 00000000..2dc24204 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java @@ -0,0 +1,31 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.inventory.ItemFactory; + +/** + * Represents leather armor ({@link Material#LEATHER_BOOTS}, {@link + * Material#LEATHER_CHESTPLATE}, {@link Material#LEATHER_HELMET}, or {@link + * Material#LEATHER_LEGGINGS}) that can be colored. + */ +public interface LeatherArmorMeta extends ItemMeta { + + /** + * Gets the color of the armor. If it has not been set otherwise, it will + * be {@link ItemFactory#getDefaultLeatherColor()}. + * + * @return the color of the armor, never null + */ + Color getColor(); + + /** + * Sets the color of the armor. + * + * @param color the color to set. Setting it to null is equivalent to + * setting it to {@link ItemFactory#getDefaultLeatherColor()}. + */ + void setColor(Color color); + + LeatherArmorMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/MapMeta.java b/src/main/java/org/bukkit/inventory/meta/MapMeta.java new file mode 100644 index 00000000..a0e60142 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/MapMeta.java @@ -0,0 +1,76 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; + +/** + * Represents a map that can be scalable. + */ +public interface MapMeta extends ItemMeta { + + /** + * Checks to see if this map is scaling. + * + * @return true if this map is scaling + */ + boolean isScaling(); + + /** + * Sets if this map is scaling or not. + * + * @param value true to scale + */ + void setScaling(boolean value); + + /** + * Checks for existence of a location name. + * + * @return true if this has a location name + */ + boolean hasLocationName(); + + /** + * Gets the location name that is set. + *

    + * Plugins should check that hasLocationName() returns true + * before calling this method. + * + * @return the location name that is set + */ + String getLocationName(); + + /** + * Sets the location name. A custom map color will alter the display of the + * map in an inventory slot. + * + * @param name the name to set + */ + void setLocationName(String name); + + /** + * Checks for existence of a map color. + * + * @return true if this has a custom map color + */ + boolean hasColor(); + + /** + * Gets the map color that is set. A custom map color will alter the display + * of the map in an inventory slot. + *

    + * Plugins should check that hasColor() returns true before + * calling this method. + * + * @return the map color that is set + */ + Color getColor(); + + /** + * Sets the map color. A custom map color will alter the display of the map + * in an inventory slot. + * + * @param color the color to set + */ + void setColor(Color color); + + MapMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/PotionMeta.java b/src/main/java/org/bukkit/inventory/meta/PotionMeta.java new file mode 100644 index 00000000..944d5141 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/PotionMeta.java @@ -0,0 +1,121 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionType; + +import java.util.List; + +/** + * Represents a potion or item that can have custom effects. + */ +public interface PotionMeta extends ItemMeta { + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + PotionData getBasePotionData(); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this potion. + *

    + * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + List getCustomEffects(); + + /** + * Adds a custom potion effect to this potion. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the potion meta changed as a result of this call + */ + boolean addCustomEffect(PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this potion. + * + * @param type the potion effect type to remove + * @return true if the potion meta changed as a result of this call + */ + boolean removeCustomEffect(PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this potion. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(PotionEffectType type); + + /** + * Moves a potion effect to the top of the potion effect list. + *

    + * This causes the client to display the potion effect in the potion's name. + * + * @param type the potion effect type to move + * @return true if the potion meta changed as a result of this call + * @deprecated use {@link PotionType#PotionType} + */ + @Deprecated + boolean setMainEffect(PotionEffectType type); + + /** + * Removes all custom potion effects from this potion. + * + * @return true if the potion meta changed as a result of this call + */ + boolean clearCustomEffects(); + + /** + * Checks for existence of a potion color. + * + * @return true if this has a custom potion color + */ + boolean hasColor(); + + /** + * Gets the potion color that is set. A custom potion color will alter the + * display of the potion in an inventory slot. + *

    + * Plugins should check that hasColor() returns true before + * calling this method. + * + * @return the potion color that is set + */ + Color getColor(); + + /** + * Sets the potion color. A custom potion color will alter the display of + * the potion in an inventory slot. + * + * @param color the color to set + */ + void setColor(Color color); + + @Override + PotionMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/Repairable.java b/src/main/java/org/bukkit/inventory/meta/Repairable.java new file mode 100644 index 00000000..c49844ed --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/Repairable.java @@ -0,0 +1,31 @@ +package org.bukkit.inventory.meta; + +/** + * Represents an item that can be repaired at an anvil. + */ +public interface Repairable { + + /** + * Checks to see if this has a repair penalty + * + * @return true if this has a repair penalty + */ + boolean hasRepairCost(); + + /** + * Gets the repair penalty + * + * @return the repair penalty + */ + int getRepairCost(); + + /** + * Sets the repair penalty + * + * @param cost repair penalty + */ + void setRepairCost(int cost); + + @SuppressWarnings("javadoc") + Repairable clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/SkullMeta.java b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java new file mode 100644 index 00000000..34bc7e61 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java @@ -0,0 +1,59 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; + +/** + * Represents a skull ({@link Material#SKULL_ITEM}) that can have an owner. + */ +public interface SkullMeta extends ItemMeta { + + /** + * Gets the owner of the skull. + * + * @return the owner if the skull + * @deprecated see {@link #setOwningPlayer(OfflinePlayer)}. + */ + @Deprecated + String getOwner(); + + /** + * Checks to see if the skull has an owner. + * + * @return true if the skull has an owner + */ + boolean hasOwner(); + + /** + * Sets the owner of the skull. + *

    + * Plugins should check that hasOwner() returns true before calling this + * plugin. + * + * @param owner the new owner of the skull + * @return true if the owner was successfully set + * @deprecated see {@link #setOwningPlayer(OfflinePlayer)}. + */ + @Deprecated + boolean setOwner(String owner); + + /** + * Gets the owner of the skull. + * + * @return the owner if the skull + */ + OfflinePlayer getOwningPlayer(); + + /** + * Sets the owner of the skull. + *

    + * Plugins should check that hasOwner() returns true before calling this + * plugin. + * + * @param owner the new owner of the skull + * @return true if the owner was successfully set + */ + boolean setOwningPlayer(OfflinePlayer owner); + + SkullMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java b/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java new file mode 100644 index 00000000..b89802d1 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java @@ -0,0 +1,28 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +/** + * Represents a {@link Material#MONSTER_EGG} and it's spawned type. + */ +public interface SpawnEggMeta extends ItemMeta { + + /** + * Get the type of entity this egg will spawn. + * + * @return The entity type. May be null for implementation specific default. + */ + EntityType getSpawnedType(); + + /** + * Set the type of entity this egg will spawn. + * + * @param type The entity type. May be null for implementation specific + * default. + */ + void setSpawnedType(EntityType type); + + @Override + SpawnEggMeta clone(); +} diff --git a/src/main/java/org/bukkit/map/MapCanvas.java b/src/main/java/org/bukkit/map/MapCanvas.java new file mode 100644 index 00000000..d68bb179 --- /dev/null +++ b/src/main/java/org/bukkit/map/MapCanvas.java @@ -0,0 +1,84 @@ +package org.bukkit.map; + +import java.awt.Image; + +/** + * Represents a canvas for drawing to a map. Each canvas is associated with a + * specific {@link MapRenderer} and represents that renderer's layer on the + * map. + */ +public interface MapCanvas { + + /** + * Get the map this canvas is attached to. + * + * @return The MapView this canvas is attached to. + */ + public MapView getMapView(); + + /** + * Get the cursor collection associated with this canvas. + * + * @return The MapCursorCollection associated with this canvas. + */ + public MapCursorCollection getCursors(); + + /** + * Set the cursor collection associated with this canvas. This does not + * usually need to be called since a MapCursorCollection is already + * provided. + * + * @param cursors The MapCursorCollection to associate with this canvas. + */ + public void setCursors(MapCursorCollection cursors); + + /** + * Draw a pixel to the canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @param color The color. See {@link MapPalette}. + */ + public void setPixel(int x, int y, byte color); + + /** + * Get a pixel from the canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @return The color. See {@link MapPalette}. + */ + public byte getPixel(int x, int y); + + /** + * Get a pixel from the layers below this canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @return The color. See {@link MapPalette}. + */ + public byte getBasePixel(int x, int y); + + /** + * Draw an image to the map. The image will be clipped if necessary. + * + * @param x The x coordinate of the image. + * @param y The y coordinate of the image. + * @param image The Image to draw. + */ + public void drawImage(int x, int y, Image image); + + /** + * Render text to the map using fancy formatting. Newline (\n) characters + * will move down one line and return to the original column, and the text + * color can be changed using sequences such as "§12;", replacing 12 with + * the palette index of the color (see {@link MapPalette}). + * + * @param x The column to start rendering on. + * @param y The row to start rendering on. + * @param font The font to use. + * @param text The formatted text to render. + */ + public void drawText(int x, int y, MapFont font, String text); + +} diff --git a/src/main/java/org/bukkit/map/MapCursor.java b/src/main/java/org/bukkit/map/MapCursor.java new file mode 100644 index 00000000..b93886c6 --- /dev/null +++ b/src/main/java/org/bukkit/map/MapCursor.java @@ -0,0 +1,214 @@ +package org.bukkit.map; + +/** + * Represents a cursor on a map. + */ +public final class MapCursor { + private byte x, y; + private byte direction, type; + private boolean visible; + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + * @deprecated Magic value + */ + @Deprecated + public MapCursor(byte x, byte y, byte direction, byte type, boolean visible) { + this.x = x; + this.y = y; + setDirection(direction); + setRawType(type); + this.visible = visible; + } + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + */ + public MapCursor(byte x, byte y, byte direction, Type type, boolean visible) { + this.x = x; + this.y = y; + setDirection(direction); + setType(type); + this.visible = visible; + } + + /** + * Get the X position of this cursor. + * + * @return The X coordinate. + */ + public byte getX() { + return x; + } + + /** + * Get the Y position of this cursor. + * + * @return The Y coordinate. + */ + public byte getY() { + return y; + } + + /** + * Get the direction of this cursor. + * + * @return The facing of the cursor, from 0 to 15. + */ + public byte getDirection() { + return direction; + } + + /** + * Get the type of this cursor. + * + * @return The type (color/style) of the map cursor. + */ + public Type getType() { + return Type.byValue(type); + } + + /** + * Get the type of this cursor. + * + * @return The type (color/style) of the map cursor. + * @deprecated Magic value + */ + @Deprecated + public byte getRawType() { + return type; + } + + /** + * Get the visibility status of this cursor. + * + * @return True if visible, false otherwise. + */ + public boolean isVisible() { + return visible; + } + + /** + * Set the X position of this cursor. + * + * @param x The X coordinate. + */ + public void setX(byte x) { + this.x = x; + } + + /** + * Set the Y position of this cursor. + * + * @param y The Y coordinate. + */ + public void setY(byte y) { + this.y = y; + } + + /** + * Set the direction of this cursor. + * + * @param direction The facing of the cursor, from 0 to 15. + */ + public void setDirection(byte direction) { + if (direction < 0 || direction > 15) { + throw new IllegalArgumentException("Direction must be in the range 0-15"); + } + this.direction = direction; + } + + /** + * Set the type of this cursor. + * + * @param type The type (color/style) of the map cursor. + */ + public void setType(Type type) { + setRawType(type.value); + } + + /** + * Set the type of this cursor. + * + * @param type The type (color/style) of the map cursor. + * @deprecated Magic value + */ + @Deprecated + public void setRawType(byte type) { + if (type < 0 || type > 15) { + throw new IllegalArgumentException("Type must be in the range 0-15"); + } + this.type = type; + } + + /** + * Set the visibility status of this cursor. + * + * @param visible True if visible. + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + /** + * Represents the standard types of map cursors. More may be made + * available by resource packs - the value is used by the client as an + * index in the file './misc/mapicons.png' from minecraft.jar or from a + * resource pack. + */ + public enum Type { + WHITE_POINTER(0), + GREEN_POINTER(1), + RED_POINTER(2), + BLUE_POINTER(3), + WHITE_CROSS(4), + RED_MARKER(5), + WHITE_CIRCLE(6), + SMALL_WHITE_CIRCLE(7), + MANSION(8), + TEMPLE(9); + + private byte value; + + private Type(int value) { + this.value = (byte) value; + } + + /** + * + * @return the value + * @deprecated Magic value + */ + @Deprecated + public byte getValue() { + return value; + } + + /** + * + * @param value the value + * @return the matching type + * @deprecated Magic value + */ + @Deprecated + public static Type byValue(byte value) { + for (Type t : values()) { + if (t.value == value) return t; + } + return null; + } + } + +} diff --git a/src/main/java/org/bukkit/map/MapCursorCollection.java b/src/main/java/org/bukkit/map/MapCursorCollection.java new file mode 100644 index 00000000..1dc9025d --- /dev/null +++ b/src/main/java/org/bukkit/map/MapCursorCollection.java @@ -0,0 +1,96 @@ +package org.bukkit.map; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents all the map cursors on a {@link MapCanvas}. Like MapCanvas, a + * MapCursorCollection is linked to a specific {@link MapRenderer}. + */ +public final class MapCursorCollection { + private List cursors = new ArrayList(); + + /** + * Get the amount of cursors in this collection. + * + * @return The size of this collection. + */ + public int size() { + return cursors.size(); + } + + /** + * Get a cursor from this collection. + * + * @param index The index of the cursor. + * @return The MapCursor. + */ + public MapCursor getCursor(int index) { + return cursors.get(index); + } + + /** + * Remove a cursor from the collection. + * + * @param cursor The MapCursor to remove. + * @return Whether the cursor was removed successfully. + */ + public boolean removeCursor(MapCursor cursor) { + return cursors.remove(cursor); + } + + /** + * Add a cursor to the collection. + * + * @param cursor The MapCursor to add. + * @return The MapCursor that was passed. + */ + public MapCursor addCursor(MapCursor cursor) { + cursors.add(cursor); + return cursor; + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @return The newly added MapCursor. + */ + public MapCursor addCursor(int x, int y, byte direction) { + return addCursor(x, y, direction, (byte) 0, true); + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @return The newly added MapCursor. + * @deprecated Magic value + */ + @Deprecated + public MapCursor addCursor(int x, int y, byte direction, byte type) { + return addCursor(x, y, direction, type, true); + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible. + * @return The newly added MapCursor. + * @deprecated Magic value + */ + @Deprecated + public MapCursor addCursor(int x, int y, byte direction, byte type, boolean visible) { + return addCursor(new MapCursor((byte) x, (byte) y, direction, type, visible)); + } + +} diff --git a/src/main/java/org/bukkit/map/MapFont.java b/src/main/java/org/bukkit/map/MapFont.java new file mode 100644 index 00000000..2d3b2da2 --- /dev/null +++ b/src/main/java/org/bukkit/map/MapFont.java @@ -0,0 +1,147 @@ +package org.bukkit.map; + +import java.util.HashMap; +import org.bukkit.ChatColor; + +/** + * Represents a bitmap font drawable to a map. + */ +public class MapFont { + + private final HashMap chars = new HashMap(); + private int height = 0; + protected boolean malleable = true; + + /** + * Set the sprite for a given character. + * + * @param ch The character to set the sprite for. + * @param sprite The CharacterSprite to set. + * @throws IllegalStateException if this font is static. + */ + public void setChar(char ch, CharacterSprite sprite) { + if (!malleable) { + throw new IllegalStateException("this font is not malleable"); + } + + chars.put(ch, sprite); + if (sprite.getHeight() > height) { + height = sprite.getHeight(); + } + } + + /** + * Get the sprite for a given character. + * + * @param ch The character to get the sprite for. + * @return The CharacterSprite associated with the character, or null if + * there is none. + */ + public CharacterSprite getChar(char ch) { + return chars.get(ch); + } + + /** + * Get the width of the given text as it would be rendered using this + * font. + * + * @param text The text. + * @return The width in pixels. + */ + public int getWidth(String text) { + if (!isValid(text)) { + throw new IllegalArgumentException("text contains invalid characters"); + } + + if (text.length() == 0) { + return 0; + } + + int result = 0; + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == ChatColor.COLOR_CHAR) continue; + result += chars.get(ch).getWidth(); + } + result += text.length() - 1; // Account for 1px spacing between characters + + return result; + } + + /** + * Get the height of this font. + * + * @return The height of the font. + */ + public int getHeight() { + return height; + } + + /** + * Check whether the given text is valid. + * + * @param text The text. + * @return True if the string contains only defined characters, false + * otherwise. + */ + public boolean isValid(String text) { + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == ChatColor.COLOR_CHAR || ch == '\n') continue; + if (chars.get(ch) == null) return false; + } + return true; + } + + /** + * Represents the graphics for a single character in a MapFont. + */ + public static class CharacterSprite { + + private final int width; + private final int height; + private final boolean[] data; + + public CharacterSprite(int width, int height, boolean[] data) { + this.width = width; + this.height = height; + this.data = data; + + if (data.length != width * height) { + throw new IllegalArgumentException("size of data does not match dimensions"); + } + } + + /** + * Get the value of a pixel of the character. + * + * @param row The row, in the range [0,8). + * @param col The column, in the range [0,8). + * @return True if the pixel is solid, false if transparent. + */ + public boolean get(int row, int col) { + if (row < 0 || col < 0 || row >= height || col >= width) return false; + return data[row * width + col]; + } + + /** + * Get the width of the character sprite. + * + * @return The width of the character. + */ + public int getWidth() { + return width; + } + + /** + * Get the height of the character sprite. + * + * @return The height of the character. + */ + public int getHeight() { + return height; + } + + } + +} diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java new file mode 100644 index 00000000..2c27d0c9 --- /dev/null +++ b/src/main/java/org/bukkit/map/MapPalette.java @@ -0,0 +1,256 @@ +package org.bukkit.map; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; + +/** + * Represents the palette that map items use. + *

    + * These fields are hee base color ranges. Each entry corresponds to four + * colors of varying shades with values entry to entry + 3. + */ +public final class MapPalette { + // Internal mechanisms + private MapPalette() {} + + private static Color c(int r, int g, int b) { + return new Color(r, g, b); + } + + private static double getDistance(Color c1, Color c2) { + double rmean = (c1.getRed() + c2.getRed()) / 2.0; + double r = c1.getRed() - c2.getRed(); + double g = c1.getGreen() - c2.getGreen(); + int b = c1.getBlue() - c2.getBlue(); + double weightR = 2 + rmean / 256.0; + double weightG = 4.0; + double weightB = 2 + (255 - rmean) / 256.0; + return weightR * r * r + weightG * g * g + weightB * b * b; + } + + static final Color[] colors = { + c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), + c(89, 125, 39), c(109, 153, 48), c(127, 178, 56), c(67, 94, 29), + c(174, 164, 115), c(213, 201, 140), c(247, 233, 163), c(130, 123, 86), + c(140, 140, 140), c(171, 171, 171), c(199, 199, 199), c(105, 105, 105), + c(180, 0, 0), c(220, 0, 0), c(255, 0, 0), c(135, 0, 0), + c(112, 112, 180), c(138, 138, 220), c(160, 160, 255), c(84, 84, 135), + c(117, 117, 117), c(144, 144, 144), c(167, 167, 167), c(88, 88, 88), + c(0, 87, 0), c(0, 106, 0), c(0, 124, 0), c(0, 65, 0), + c(180, 180, 180), c(220, 220, 220), c(255, 255, 255), c(135, 135, 135), + c(115, 118, 129), c(141, 144, 158), c(164, 168, 184), c(86, 88, 97), + c(106, 76, 54), c(130, 94, 66), c(151, 109, 77), c(79, 57, 40), + c(79, 79, 79), c(96, 96, 96), c(112, 112, 112), c(59, 59, 59), + c(45, 45, 180), c(55, 55, 220), c(64, 64, 255), c(33, 33, 135), + c(100, 84, 50), c(123, 102, 62), c(143, 119, 72), c(75, 63, 38), + c(180, 177, 172), c(220, 217, 211), c(255, 252, 245), c(135, 133, 129), + c(152, 89, 36), c(186, 109, 44), c(216, 127, 51), c(114, 67, 27), + c(125, 53, 152), c(153, 65, 186), c(178, 76, 216), c(94, 40, 114), + c(72, 108, 152), c(88, 132, 186), c(102, 153, 216), c(54, 81, 114), + c(161, 161, 36), c(197, 197, 44), c(229, 229, 51), c(121, 121, 27), + c(89, 144, 17), c(109, 176, 21), c(127, 204, 25), c(67, 108, 13), + c(170, 89, 116), c(208, 109, 142), c(242, 127, 165), c(128, 67, 87), + c(53, 53, 53), c(65, 65, 65), c(76, 76, 76), c(40, 40, 40), + c(108, 108, 108), c(132, 132, 132), c(153, 153, 153), c(81, 81, 81), + c(53, 89, 108), c(65, 109, 132), c(76, 127, 153), c(40, 67, 81), + c(89, 44, 125), c(109, 54, 153), c(127, 63, 178), c(67, 33, 94), + c(36, 53, 125), c(44, 65, 153), c(51, 76, 178), c(27, 40, 94), + c(72, 53, 36), c(88, 65, 44), c(102, 76, 51), c(54, 40, 27), + c(72, 89, 36), c(88, 109, 44), c(102, 127, 51), c(54, 67, 27), + c(108, 36, 36), c(132, 44, 44), c(153, 51, 51), c(81, 27, 27), + c(17, 17, 17), c(21, 21, 21), c(25, 25, 25), c(13, 13, 13), + c(176, 168, 54), c(215, 205, 66), c(250, 238, 77), c(132, 126, 40), + c(64, 154, 150), c(79, 188, 183), c(92, 219, 213), c(48, 115, 112), + c(52, 90, 180), c(63, 110, 220), c(74, 128, 255), c(39, 67, 135), + c(0, 153, 40), c(0, 187, 50), c(0, 217, 58), c(0, 114, 30), + c(91, 60, 34), c(111, 74, 42), c(129, 86, 49), c(68, 45, 25), + c(79, 1, 0), c(96, 1, 0), c(112, 2, 0), c(59, 1, 0), + c(147, 124, 113), c(180, 152, 138), c(209, 177, 161), c(110, 93, 85), + c(112, 57, 25), c(137, 70, 31), c(159, 82, 36), c(84, 43, 19), + c(105, 61, 76), c(128, 75, 93), c(149, 87, 108), c(78, 46, 57), + c(79, 76, 97), c(96, 93, 119), c(112, 108, 138), c(59, 57, 73), + c(131, 93, 25), c(160, 114, 31), c(186, 133, 36), c(98, 70, 19), + c(72, 82, 37), c(88, 100, 45), c(103, 117, 53), c(54, 61, 28), + c(112, 54, 55), c(138, 66, 67), c(160, 77, 78), c(84, 40, 41), + c(40, 28, 24), c(49, 35, 30), c(57, 41, 35), c(30, 21, 18), + c(95, 75, 69), c(116, 92, 84), c(135, 107, 98), c(71, 56, 51), + c(61, 64, 64), c(75, 79, 79), c(87, 92, 92), c(46, 48, 48), + c(86, 51, 62), c(105, 62, 75), c(122, 73, 88), c(64, 38, 46), + c(53, 43, 64), c(65, 53, 79), c(76, 62, 92), c(40, 32, 48), + c(53, 35, 24), c(65, 43, 30), c(76, 50, 35), c(40, 26, 18), + c(53, 57, 29), c(65, 70, 36), c(76, 82, 42), c(40, 43, 22), + c(100, 42, 32), c(122, 51, 39), c(142, 60, 46), c(75, 31, 24), + c(26, 15, 11), c(31, 18, 13), c(37, 22, 16), c(19, 11, 8) + }; + + // Interface + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte TRANSPARENT = 0; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_GREEN = 4; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_BROWN = 8; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte GRAY_1 = 12; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte RED = 16; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte PALE_BLUE = 20; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte GRAY_2 = 24; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_GREEN = 28; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte WHITE = 32; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_GRAY = 36; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte BROWN = 40; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_GRAY = 44; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte BLUE = 48; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_BROWN = 52; + + /** + * Resize an image to 128x128. + * + * @param image The image to resize. + * @return The resized image. + */ + public static BufferedImage resizeImage(Image image) { + BufferedImage result = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = result.createGraphics(); + graphics.drawImage(image, 0, 0, 128, 128, null); + graphics.dispose(); + return result; + } + + /** + * Convert an Image to a byte[] using the palette. + * + * @param image The image to convert. + * @return A byte[] containing the pixels of the image. + * @deprecated Magic value + */ + @Deprecated + public static byte[] imageToBytes(Image image) { + BufferedImage temp = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = temp.createGraphics(); + graphics.drawImage(image, 0, 0, null); + graphics.dispose(); + + int[] pixels = new int[temp.getWidth() * temp.getHeight()]; + temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); + + byte[] result = new byte[temp.getWidth() * temp.getHeight()]; + for (int i = 0; i < pixels.length; i++) { + result[i] = matchColor(new Color(pixels[i], true)); + } + return result; + } + + /** + * Get the index of the closest matching color in the palette to the given + * color. + * + * @param r The red component of the color. + * @param b The blue component of the color. + * @param g The green component of the color. + * @return The index in the palette. + * @deprecated Magic value + */ + @Deprecated + public static byte matchColor(int r, int g, int b) { + return matchColor(new Color(r, g, b)); + } + + /** + * Get the index of the closest matching color in the palette to the given + * color. + * + * @param color The Color to match. + * @return The index in the palette. + * @deprecated Magic value + */ + @Deprecated + public static byte matchColor(Color color) { + if (color.getAlpha() < 128) return 0; + + int index = 0; + double best = -1; + + for (int i = 4; i < colors.length; i++) { + double distance = getDistance(color, colors[i]); + if (distance < best || best == -1) { + best = distance; + index = i; + } + } + + // Minecraft has 143 colors, some of which have negative byte representations + return (byte) (index < 128 ? index : -129 + (index - 127)); + } + + /** + * Get the value of the given color in the palette. + * + * @param index The index in the palette. + * @return The Color of the palette entry. + * @deprecated Magic value + */ + @Deprecated + public static Color getColor(byte index) { + if ((index > -49 && index < 0) || index > 127) { + throw new IndexOutOfBoundsException(); + } else { + // Minecraft has 143 colors, some of which have negative byte representations + return colors[index >= 0 ? index : index + 256]; + } + } +} diff --git a/src/main/java/org/bukkit/map/MapRenderer.java b/src/main/java/org/bukkit/map/MapRenderer.java new file mode 100644 index 00000000..322d0ce3 --- /dev/null +++ b/src/main/java/org/bukkit/map/MapRenderer.java @@ -0,0 +1,56 @@ +package org.bukkit.map; + +import org.bukkit.entity.Player; + +/** + * Represents a renderer for a map. + */ +public abstract class MapRenderer { + + private boolean contextual; + + /** + * Initialize the map renderer base to be non-contextual. See {@link + * #isContextual()}. + */ + public MapRenderer() { + this(false); + } + + /** + * Initialize the map renderer base with the given contextual status. + * + * @param contextual Whether the renderer is contextual. See {@link + * #isContextual()}. + */ + public MapRenderer(boolean contextual) { + this.contextual = contextual; + } + + /** + * Get whether the renderer is contextual, i.e. has different canvases for + * different players. + * + * @return True if contextual, false otherwise. + */ + final public boolean isContextual() { + return contextual; + } + + /** + * Initialize this MapRenderer for the given map. + * + * @param map The MapView being initialized. + */ + public void initialize(MapView map) {} + + /** + * Render to the given map. + * + * @param map The MapView being rendered to. + * @param canvas The canvas to use for rendering. + * @param player The player who triggered the rendering. + */ + abstract public void render(MapView map, MapCanvas canvas, Player player); + +} diff --git a/src/main/java/org/bukkit/map/MapView.java b/src/main/java/org/bukkit/map/MapView.java new file mode 100644 index 00000000..18a9936f --- /dev/null +++ b/src/main/java/org/bukkit/map/MapView.java @@ -0,0 +1,172 @@ +package org.bukkit.map; + +import java.util.List; +import org.bukkit.World; + +/** + * Represents a map item. + */ +public interface MapView { + + /** + * An enum representing all possible scales a map can be set to. + */ + public static enum Scale { + CLOSEST(0), + CLOSE(1), + NORMAL(2), + FAR(3), + FARTHEST(4); + + private byte value; + + private Scale(int value) { + this.value = (byte) value; + } + + /** + * Get the scale given the raw value. + * + * @param value The raw scale + * @return The enum scale, or null for an invalid input + * @deprecated Magic value + */ + @Deprecated + public static Scale valueOf(byte value) { + switch (value) { + case 0: return CLOSEST; + case 1: return CLOSE; + case 2: return NORMAL; + case 3: return FAR; + case 4: return FARTHEST; + default: return null; + } + } + + /** + * Get the raw value of this scale level. + * + * @return The scale value + * @deprecated Magic value + */ + @Deprecated + public byte getValue() { + return value; + } + } + + /** + * Get the ID of this map item. Corresponds to the damage value of a map + * in an inventory. + * + * @return The ID of the map. + * @deprecated Magic value + */ + @Deprecated + public short getId(); + + /** + * Check whether this map is virtual. A map is virtual if its lowermost + * MapRenderer is plugin-provided. + * + * @return Whether the map is virtual. + */ + public boolean isVirtual(); + + /** + * Get the scale of this map. + * + * @return The scale of the map. + */ + public Scale getScale(); + + /** + * Set the scale of this map. + * + * @param scale The scale to set. + */ + public void setScale(Scale scale); + + /** + * Get the center X position of this map. + * + * @return The center X position. + */ + public int getCenterX(); + + /** + * Get the center Z position of this map. + * + * @return The center Z position. + */ + public int getCenterZ(); + + /** + * Set the center X position of this map. + * + * @param x The center X position. + */ + public void setCenterX(int x); + + /** + * Set the center Z position of this map. + * + * @param z The center Z position. + */ + public void setCenterZ(int z); + + /** + * Get the world that this map is associated with. Primarily used by the + * internal renderer, but may be used by external renderers. May return + * null if the world the map is associated with is not loaded. + * + * @return The World this map is associated with. + */ + public World getWorld(); + + /** + * Set the world that this map is associated with. The world is used by + * the internal renderer, and may also be used by external renderers. + * + * @param world The World to associate this map with. + */ + public void setWorld(World world); + + /** + * Get a list of MapRenderers currently in effect. + * + * @return A {@code List} containing each map renderer. + */ + public List getRenderers(); + + /** + * Add a renderer to this map. + * + * @param renderer The MapRenderer to add. + */ + public void addRenderer(MapRenderer renderer); + + /** + * Remove a renderer from this map. + * + * @param renderer The MapRenderer to remove. + * @return True if the renderer was successfully removed. + */ + public boolean removeRenderer(MapRenderer renderer); + + /** + * Whether the map will show a smaller position cursor (true), or no + * position cursor (false) when cursor is outside of map's range. + * + * @return unlimited tracking state + */ + boolean isUnlimitedTracking(); + + /** + * Whether the map will show a smaller position cursor (true), or no + * position cursor (false) when cursor is outside of map's range. + * + * @param unlimited tracking state + */ + void setUnlimitedTracking(boolean unlimited); +} diff --git a/src/main/java/org/bukkit/map/MinecraftFont.java b/src/main/java/org/bukkit/map/MinecraftFont.java new file mode 100644 index 00000000..9ec8d10f --- /dev/null +++ b/src/main/java/org/bukkit/map/MinecraftFont.java @@ -0,0 +1,328 @@ +package org.bukkit.map; + +/** + * Represents the built-in Minecraft font. + */ +public class MinecraftFont extends MapFont { + + private static final int spaceSize = 2; + + private static final String fontChars = + " !\"#$%&'()*+,-./0123456789:;<=>?" + + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "'abcdefghijklmnopqrstuvwxyz{|}~\u007F" + + "\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7" + // Çüéâäàåç + "\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5" + // êëèïîìÄÅ + "\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9" + // ÉæÆôöòûù + "\u00FF\u00D6\u00DC\u00F8\u00A3\u00D8\u00D7\u0191" + // ÿÖÜø£Ø׃ + "\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA" + // áíóúñѪº + "\u00BF\u00AE\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB"; // ¿®¬½¼¡«» + + private static final int[][] fontData = new int[][] { + /* null */ {0,0,0,0,0,0,0,0}, + /* 1 */ {126,129,165,129,189,153,129,126}, + /* 2 */ {126,255,219,255,195,231,255,126}, + /* 3 */ {54,127,127,127,62,28,8,0}, + /* 4 */ {8,28,62,127,62,28,8,0}, + /* 5 */ {28,62,28,127,127,62,28,62}, + /* 6 */ {8,8,28,62,127,62,28,62}, + /* 7 */ {0,0,24,60,60,24,0,0}, + /* 8 */ {255,255,231,195,195,231,255,255}, + /* 9 */ {0,60,102,66,66,102,60,0}, + /* 10 */ {255,195,153,189,189,153,195,255}, + /* 11 */ {240,224,240,190,51,51,51,30}, + /* 12 */ {60,102,102,102,60,24,126,24}, + /* 13 */ {252,204,252,12,12,14,15,7}, + /* 14 */ {254,198,254,198,198,230,103,3}, + /* 15 */ {153,90,60,231,231,60,90,153}, + /* 16 */ {1,7,31,127,31,7,1,0}, + /* 17 */ {64,112,124,127,124,112,64,0}, + /* 18 */ {24,60,126,24,24,126,60,24}, + /* 19 */ {102,102,102,102,102,0,102,0}, + /* 20 */ {254,219,219,222,216,216,216,0}, + /* 21 */ {124,198,28,54,54,28,51,30}, + /* 22 */ {0,0,0,0,126,126,126,0}, + /* 23 */ {24,60,126,24,126,60,24,255}, + /* 24 */ {24,60,126,24,24,24,24,0}, + /* 25 */ {24,24,24,24,126,60,24,0}, + /* 26 */ {0,24,48,127,48,24,0,0}, + /* 27 */ {0,12,6,127,6,12,0,0}, + /* 28 */ {0,0,3,3,3,127,0,0}, + /* 29 */ {0,36,102,255,102,36,0,0}, + /* 30 */ {0,24,60,126,255,255,0,0}, + /* 31 */ {0,255,255,126,60,24,0,0}, + /* */ {0,0,0,0,0,0,0,0}, + /* ! */ {1,1,1,1,1,0,1,0}, + /* " */ {10,10,5,0,0,0,0,0}, + /* # */ {10,10,31,10,31,10,10,0}, + /* $ */ {4,30,1,14,16,15,4,0}, + /* % */ {17,9,8,4,2,18,17,0}, + /* & */ {4,10,4,22,13,9,22,0}, + /* ' */ {2,2,1,0,0,0,0,0}, + /* ( */ {12,2,1,1,1,2,12,0}, + /* ) */ {3,4,8,8,8,4,3,0}, + /* * */ {0,0,9,6,9,0,0,0}, + /* + */ {0,4,4,31,4,4,0,0}, + /* , */ {0,0,0,0,0,1,1,1}, + /* - */ {0,0,0,31,0,0,0,0}, + /* . */ {0,0,0,0,0,1,1,0}, + /* / */ {16,8,8,4,2,2,1,0}, + /* 0 */ {14,17,25,21,19,17,14,0}, + /* 1 */ {4,6,4,4,4,4,31,0}, + /* 2 */ {14,17,16,12,2,17,31,0}, + /* 3 */ {14,17,16,12,16,17,14,0}, + /* 4 */ {24,20,18,17,31,16,16,0}, + /* 5 */ {31,1,15,16,16,17,14,0}, + /* 6 */ {12,2,1,15,17,17,14,0}, + /* 7 */ {31,17,16,8,4,4,4,0}, + /* 8 */ {14,17,17,14,17,17,14,0}, + /* 9 */ {14,17,17,30,16,8,6,0}, + /* : */ {0,1,1,0,0,1,1,0}, + /* ; */ {0,1,1,0,0,1,1,1}, + /* < */ {8,4,2,1,2,4,8,0}, + /* = */ {0,0,31,0,0,31,0,0}, + /* > */ {1,2,4,8,4,2,1,0}, + /* ? */ {14,17,16,8,4,0,4,0}, + /* @ */ {30,33,45,45,61,1,30,0}, + /* A */ {14,17,31,17,17,17,17,0}, + /* B */ {15,17,15,17,17,17,15,0}, + /* C */ {14,17,1,1,1,17,14,0}, + /* D */ {15,17,17,17,17,17,15,0}, + /* E */ {31,1,7,1,1,1,31,0}, + /* F */ {31,1,7,1,1,1,1,0}, + /* G */ {30,1,25,17,17,17,14,0}, + /* H */ {17,17,31,17,17,17,17,0}, + /* I */ {7,2,2,2,2,2,7,0}, + /* J */ {16,16,16,16,16,17,14,0}, + /* K */ {17,9,7,9,17,17,17,0}, + /* L */ {1,1,1,1,1,1,31,0}, + /* M */ {17,27,21,17,17,17,17,0}, + /* N */ {17,19,21,25,17,17,17,0}, + /* O */ {14,17,17,17,17,17,14,0}, + /* P */ {15,17,15,1,1,1,1,0}, + /* Q */ {14,17,17,17,17,9,22,0}, + /* R */ {15,17,15,17,17,17,17,0}, + /* S */ {30,1,14,16,16,17,14,0}, + /* T */ {31,4,4,4,4,4,4,0}, + /* U */ {17,17,17,17,17,17,14,0}, + /* V */ {17,17,17,17,10,10,4,0}, + /* W */ {17,17,17,17,21,27,17,0}, + /* X */ {17,10,4,10,17,17,17,0}, + /* Y */ {17,10,4,4,4,4,4,0}, + /* Z */ {31,16,8,4,2,1,31,0}, + /* [ */ {7,1,1,1,1,1,7,0}, + /* \ */ {1,2,2,4,8,8,16,0}, + /* ] */ {7,4,4,4,4,4,7,0}, + /* ^ */ {4,10,17,0,0,0,0,0}, + /* _ */ {0,0,0,0,0,0,0,31}, + /* ` */ {1,1,2,0,0,0,0,0}, + /* a */ {0,0,14,16,30,17,30,0}, + /* b */ {1,1,13,19,17,17,15,0}, + /* c */ {0,0,14,17,1,17,14,0}, + /* d */ {16,16,22,25,17,17,30,0}, + /* e */ {0,0,14,17,31,1,30,0}, + /* f */ {12,2,15,2,2,2,2,0}, + /* g */ {0,0,30,17,17,30,16,15}, + /* h */ {1,1,13,19,17,17,17,0}, + /* i */ {1,0,1,1,1,1,1,0}, + /* j */ {16,0,16,16,16,17,17,14}, + /* k */ {1,1,9,5,3,5,9,0}, + /* l */ {1,1,1,1,1,1,2,0}, + /* m */ {0,0,11,21,21,17,17,0}, + /* n */ {0,0,15,17,17,17,17,0}, + /* o */ {0,0,14,17,17,17,14,0}, + /* p */ {0,0,13,19,17,15,1,1}, + /* q */ {0,0,22,25,17,30,16,16}, + /* r */ {0,0,13,19,1,1,1,0}, + /* s */ {0,0,30,1,14,16,15,0}, + /* t */ {2,2,7,2,2,2,4,0}, + /* u */ {0,0,17,17,17,17,30,0}, + /* v */ {0,0,17,17,17,10,4,0}, + /* w */ {0,0,17,17,21,21,30,0}, + /* x */ {0,0,17,10,4,10,17,0}, + /* y */ {0,0,17,17,17,30,16,15}, + /* z */ {0,0,31,8,4,2,31,0}, + /* { */ {12,2,2,1,2,2,12,0}, + /* | */ {1,1,1,0,1,1,1,0}, + /* } */ {3,4,4,8,4,4,3,0}, + /* ~ */ {38,25,0,0,0,0,0,0}, + /* ⌂ */ {0,0,4,10,17,17,31,0}, + /* Ç */ {14,17,1,1,17,14,16,12}, + /* ü */ {10,0,17,17,17,17,30,0}, + /* é */ {24,0,14,17,31,1,30,0}, + /* â */ {14,17,14,16,30,17,30,0}, + /* ä */ {10,0,14,16,30,17,30,0}, + /* à */ {3,0,14,16,30,17,30,0}, + /* å */ {4,0,14,16,30,17,30,0}, + /* ç */ {0,14,17,1,17,14,16,12}, + /* ê */ {14,17,14,17,31,1,30,0}, + /* ë */ {10,0,14,17,31,1,30,0}, + /* è */ {3,0,14,17,31,1,30,0}, + /* ï */ {5,0,2,2,2,2,2,0}, + /* î */ {14,17,4,4,4,4,4,0}, + /* ì */ {3,0,2,2,2,2,2,0}, + /* Ä */ {17,14,17,31,17,17,17,0}, + /* Å */ {4,0,14,17,31,17,17,0}, + /* É */ {24,0,31,1,7,1,31,0}, + /* æ */ {0,0,10,20,30,5,30,0}, + /* Æ */ {30,5,15,5,5,5,29,0}, + /* ô */ {14,17,14,17,17,17,14,0}, + /* ö */ {10,0,14,17,17,17,14,0}, + /* ò */ {3,0,14,17,17,17,14,0}, + /* û */ {14,17,0,17,17,17,30,0}, + /* ù */ {3,0,17,17,17,17,30,0}, + /* ÿ */ {10,0,17,17,17,30,16,15}, + /* Ö */ {17,14,17,17,17,17,14,0}, + /* Ü */ {17,0,17,17,17,17,14,0}, + /* ø */ {0,0,14,25,21,19,14,4}, + /* £ */ {12,18,2,15,2,2,31,0}, + /* Ø */ {14,17,25,21,19,17,14,0}, + /* × */ {0,0,5,2,5,0,0,0}, + /* ƒ */ {8,20,4,14,4,4,5,2}, + /* á */ {24,0,14,16,30,17,30,0}, + /* í */ {3,0,1,1,1,1,1,0}, + /* ó */ {24,0,14,17,17,17,14,0}, + /* ú */ {24,0,17,17,17,17,30,0}, + /* ñ */ {31,0,15,17,17,17,17,0}, + /* Ñ */ {31,0,17,19,21,25,17,0}, + /* ª */ {14,16,31,30,0,31,0,0}, + /* º */ {14,17,17,14,0,31,0,0}, + /* ¿ */ {4,0,4,2,1,17,14,0}, + /* ® */ {0,30,45,37,43,30,0,0}, + /* ¬ */ {0,0,0,31,16,16,0,0}, + /* ½ */ {17,9,8,4,18,10,25,0}, + /* ¼ */ {17,9,8,4,26,26,17,0}, + /* ¡ */ {0,1,0,1,1,1,1,0}, + /* « */ {0,20,10,5,10,20,0,0}, + /* » */ {0,5,10,20,10,5,0,0}, + /* 176 */ {68,17,68,17,68,17,68,17}, + /* 177 */ {170,85,170,85,170,85,170,85}, + /* 178 */ {219,238,219,119,219,238,219,119}, + /* 179 */ {24,24,24,24,24,24,24,24}, + /* 180 */ {24,24,24,24,31,24,24,24}, + /* 181 */ {24,24,31,24,31,24,24,24}, + /* 182 */ {108,108,108,108,111,108,108,108}, + /* 183 */ {0,0,0,0,127,108,108,108}, + /* 184 */ {0,0,31,24,31,24,24,24}, + /* 185 */ {108,108,111,96,111,108,108,108}, + /* 186 */ {108,108,108,108,108,108,108,108}, + /* 187 */ {0,0,127,96,111,108,108,108}, + /* 188 */ {108,108,111,96,127,0,0,0}, + /* 189 */ {108,108,108,108,127,0,0,0}, + /* 190 */ {24,24,31,24,31,0,0,0}, + /* 191 */ {0,0,0,0,31,24,24,24}, + /* 192 */ {24,24,24,24,248,0,0,0}, + /* 193 */ {24,24,24,24,255,0,0,0}, + /* 194 */ {0,0,0,0,255,24,24,24}, + /* 195 */ {24,24,24,24,248,24,24,24}, + /* 196 */ {0,0,0,0,255,0,0,0}, + /* 197 */ {24,24,24,24,255,24,24,24}, + /* 198 */ {24,24,248,24,248,24,24,24}, + /* 199 */ {108,108,108,108,236,108,108,108}, + /* 200 */ {108,108,236,12,252,0,0,0}, + /* 201 */ {0,0,252,12,236,108,108,108}, + /* 202 */ {108,108,239,0,255,0,0,0}, + /* 203 */ {0,0,255,0,239,108,108,108}, + /* 204 */ {108,108,236,12,236,108,108,108}, + /* 205 */ {0,0,255,0,255,0,0,0}, + /* 206 */ {108,108,239,0,239,108,108,108}, + /* 207 */ {24,24,255,0,255,0,0,0}, + /* 208 */ {108,108,108,108,255,0,0,0}, + /* 209 */ {0,0,255,0,255,24,24,24}, + /* 210 */ {0,0,0,0,255,108,108,108}, + /* 211 */ {108,108,108,108,252,0,0,0}, + /* 212 */ {24,24,248,24,248,0,0,0}, + /* 213 */ {0,0,248,24,248,24,24,24}, + /* 214 */ {0,0,0,0,252,108,108,108}, + /* 215 */ {108,108,108,108,255,108,108,108}, + /* 216 */ {24,24,255,24,255,24,24,24}, + /* 217 */ {24,24,24,24,31,0,0,0}, + /* 218 */ {0,0,0,0,248,24,24,24}, + /* 219 */ {255,255,255,255,255,255,255,255}, + /* 220 */ {0,0,0,0,255,255,255,255}, + /* 221 */ {15,15,15,15,15,15,15,15}, + /* 222 */ {240,240,240,240,240,240,240,240}, + /* 223 */ {255,255,255,255,0,0,0,0}, + /* 224 */ {0,0,110,59,19,59,110,0}, + /* 225 */ {0,30,51,31,51,31,3,3}, + /* 226 */ {0,63,51,3,3,3,3,0}, + /* 227 */ {0,127,54,54,54,54,54,0}, + /* 228 */ {63,51,6,12,6,51,63,0}, + /* 229 */ {0,0,126,27,27,27,14,0}, + /* 230 */ {0,102,102,102,102,62,6,3}, + /* 231 */ {0,110,59,24,24,24,24,0}, + /* 232 */ {63,12,30,51,51,30,12,63}, + /* 233 */ {28,54,99,127,99,54,28,0}, + /* 234 */ {28,54,99,99,54,54,119,0}, + /* 235 */ {56,12,24,62,51,51,30,0}, + /* 236 */ {0,0,126,219,219,126,0,0}, + /* 237 */ {96,48,126,219,219,126,6,3}, + /* 238 */ {28,6,3,31,3,6,28,0}, + /* 239 */ {30,51,51,51,51,51,51,0}, + /* 240 */ {0,63,0,63,0,63,0,0}, + /* 241 */ {12,12,63,12,12,0,63,0}, + /* 242 */ {6,12,24,12,6,0,63,0}, + /* 243 */ {24,12,6,12,24,0,63,0}, + /* 244 */ {112,216,216,24,24,24,24,24}, + /* 245 */ {24,24,24,24,24,27,27,14}, + /* 246 */ {12,12,0,63,0,12,12,0}, + /* 247 */ {0,110,59,0,110,59,0,0}, + /* 248 */ {28,54,54,28,0,0,0,0}, + /* 249 */ {0,0,0,24,24,0,0,0}, + /* 250 */ {0,0,0,0,24,0,0,0}, + /* 251 */ {240,48,48,48,55,54,60,56}, + /* 252 */ {30,54,54,54,54,0,0,0}, + /* 253 */ {14,24,12,6,30,0,0,0}, + /* 254 */ {0,0,60,60,60,60,0,0}, + /* 255 */ {0,0,0,0,0,0,0,0}, + }; + + /** + * A static non-malleable MinecraftFont. + */ + public static final MinecraftFont Font = new MinecraftFont(false); + + /** + * Initialize a new MinecraftFont. + */ + public MinecraftFont() { + this(true); + } + + private MinecraftFont(boolean malleable) { + for (int i = 1; i < fontData.length; ++i) { + char ch = (char) i; + if (i >= 32 && i < 32 + fontChars.length()) { + ch = fontChars.charAt(i - 32); + } + + if (ch == ' ') { + setChar(ch, new CharacterSprite(spaceSize, 8, new boolean[spaceSize * 8])); + continue; + } + + int[] rows = fontData[i]; + int width = 0; + for (int r = 0; r < 8; ++r) { + for (int c = 0; c < 8; ++c) { + if ((rows[r] & (1 << c)) != 0 && c > width) { + width = c; + } + } + } + ++width; + + boolean[] data = new boolean[width * 8]; + for (int r = 0; r < 8; ++r) { + for (int c = 0; c < width; ++c) { + data[r * width + c] = (rows[r] & (1 << c)) != 0; + } + } + + setChar(ch, new CharacterSprite(width, 8, data)); + } + + this.malleable = malleable; + } + +} diff --git a/src/main/java/org/bukkit/material/Attachable.java b/src/main/java/org/bukkit/material/Attachable.java new file mode 100644 index 00000000..1d3f1076 --- /dev/null +++ b/src/main/java/org/bukkit/material/Attachable.java @@ -0,0 +1,16 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; + +/** + * Indicates that a block can be attached to another block + */ +public interface Attachable extends Directional { + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace(); +} diff --git a/src/main/java/org/bukkit/material/Banner.java b/src/main/java/org/bukkit/material/Banner.java new file mode 100644 index 00000000..80a76165 --- /dev/null +++ b/src/main/java/org/bukkit/material/Banner.java @@ -0,0 +1,235 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +public class Banner extends MaterialData implements Attachable { + + public Banner() { + super(Material.BANNER); + } + + public Banner(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Banner(int type) { + super(type); + } + + /** + * + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Banner(Material type, byte data) { + super(type, data); + } + + /** * + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Banner(int type, byte data) { + super(type, data); + } + + public boolean isWallBanner() { + return getItemType() == Material.WALL_BANNER; + } + + public BlockFace getAttachedFace() { + if (isWallBanner()) { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } else { + return BlockFace.DOWN; + } + } + + public BlockFace getFacing() { + byte data = getData(); + + if (!isWallBanner()) { + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.SOUTH_SOUTH_WEST; + + case 0x2: + return BlockFace.SOUTH_WEST; + + case 0x3: + return BlockFace.WEST_SOUTH_WEST; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.WEST_NORTH_WEST; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_NORTH_WEST; + + case 0x8: + return BlockFace.NORTH; + + case 0x9: + return BlockFace.NORTH_NORTH_EAST; + + case 0xA: + return BlockFace.NORTH_EAST; + + case 0xB: + return BlockFace.EAST_NORTH_EAST; + + case 0xC: + return BlockFace.EAST; + + case 0xD: + return BlockFace.EAST_SOUTH_EAST; + + case 0xE: + return BlockFace.SOUTH_EAST; + + case 0xF: + return BlockFace.SOUTH_SOUTH_EAST; + } + + return null; + } else { + return getAttachedFace().getOppositeFace(); + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + if (isWallBanner()) { + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + } else { + switch (face) { + case SOUTH: + data = 0x0; + break; + + case SOUTH_SOUTH_WEST: + data = 0x1; + break; + + case SOUTH_WEST: + data = 0x2; + break; + + case WEST_SOUTH_WEST: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case WEST_NORTH_WEST: + data = 0x5; + break; + + case NORTH_WEST: + data = 0x6; + break; + + case NORTH_NORTH_WEST: + data = 0x7; + break; + + case NORTH: + data = 0x8; + break; + + case NORTH_NORTH_EAST: + data = 0x9; + break; + + case NORTH_EAST: + data = 0xA; + break; + + case EAST_NORTH_EAST: + data = 0xB; + break; + + case EAST: + data = 0xC; + break; + + case EAST_SOUTH_EAST: + data = 0xD; + break; + + case SOUTH_SOUTH_EAST: + data = 0xF; + break; + + case SOUTH_EAST: + default: + data = 0xE; + } + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Banner clone() { + return (Banner) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Bed.java b/src/main/java/org/bukkit/material/Bed.java new file mode 100644 index 00000000..ce94dafb --- /dev/null +++ b/src/main/java/org/bukkit/material/Bed.java @@ -0,0 +1,145 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a bed. + */ +public class Bed extends MaterialData implements Directional { + + /** + * Default constructor for a bed. + */ + public Bed() { + super(Material.BED_BLOCK); + } + + /** + * Instantiate a bed facing in a particular direction. + * + * @param direction the direction the bed's head is facing + */ + public Bed(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Bed(final int type) { + super(type); + } + + public Bed(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Bed(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Bed(final Material type, final byte data) { + super(type, data); + } + + /** + * Determine if this block represents the head of the bed + * + * @return true if this is the head of the bed, false if it is the foot + */ + public boolean isHeadOfBed() { + return (getData() & 0x8) == 0x8; + } + + /** + * Configure this to be either the head or the foot of the bed + * + * @param isHeadOfBed True to make it the head. + */ + public void setHeadOfBed(boolean isHeadOfBed) { + setData((byte) (isHeadOfBed ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Set which direction the head of the bed is facing. Note that this will + * only affect one of the two blocks the bed is made of. + */ + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case SOUTH: + data = 0x0; + break; + + case WEST: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case EAST: + default: + data = 0x3; + } + + if (isHeadOfBed()) { + data |= 0x8; + } + + setData(data); + } + + /** + * Get the direction that this bed's head is facing toward + * + * @return the direction the head of the bed is facing + */ + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return (isHeadOfBed() ? "HEAD" : "FOOT") + " of " + super.toString() + " facing " + getFacing(); + } + + @Override + public Bed clone() { + return (Bed) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Button.java b/src/main/java/org/bukkit/material/Button.java new file mode 100644 index 00000000..fd6a7db8 --- /dev/null +++ b/src/main/java/org/bukkit/material/Button.java @@ -0,0 +1,142 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents a button + */ +public class Button extends SimpleAttachableMaterialData implements Redstone { + public Button() { + super(Material.STONE_BUTTON); + } + + /** + * @param type the type + * @deprecated Magic value + */ + @Deprecated + public Button(final int type) { + super(type); + } + + public Button(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Button(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Button(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Sets the current state of this button + * + * @param bool + * whether or not the button is powered + */ + public void setPowered(boolean bool) { + setData((byte) (bool ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x0: + return BlockFace.UP; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + return BlockFace.DOWN; + } + + return null; + } + + /** + * Sets the direction this button is pointing toward + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case DOWN: + data |= 0x0; + break; + + case EAST: + data |= 0x1; + break; + + case WEST: + data |= 0x2; + break; + + case SOUTH: + data |= 0x3; + break; + + case NORTH: + data |= 0x4; + break; + + case UP: + data |= 0x5; + break; + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Button clone() { + return (Button) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Cake.java b/src/main/java/org/bukkit/material/Cake.java new file mode 100644 index 00000000..e72cb914 --- /dev/null +++ b/src/main/java/org/bukkit/material/Cake.java @@ -0,0 +1,93 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +public class Cake extends MaterialData { + public Cake() { + super(Material.CAKE_BLOCK); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Cake(int type) { + super(type); + } + + public Cake(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cake(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cake(Material type, byte data) { + super(type, data); + } + + /** + * Gets the number of slices eaten from this cake + * + * @return The number of slices eaten + */ + public int getSlicesEaten() { + return getData(); + } + + /** + * Gets the number of slices remaining on this cake + * + * @return The number of slices remaining + */ + public int getSlicesRemaining() { + return 6 - getData(); + } + + /** + * Sets the number of slices eaten from this cake + * + * @param n The number of slices eaten + */ + public void setSlicesEaten(int n) { + if (n < 6) { + setData((byte) n); + } // TODO: else destroy the block? Probably not possible though + } + + /** + * Sets the number of slices remaining on this cake + * + * @param n The number of slices remaining + */ + public void setSlicesRemaining(int n) { + if (n > 6) { + n = 6; + } + setData((byte) (6 - n)); + } + + @Override + public String toString() { + return super.toString() + " " + getSlicesEaten() + "/" + getSlicesRemaining() + " slices eaten/remaining"; + } + + @Override + public Cake clone() { + return (Cake) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Cauldron.java b/src/main/java/org/bukkit/material/Cauldron.java new file mode 100644 index 00000000..0d00e501 --- /dev/null +++ b/src/main/java/org/bukkit/material/Cauldron.java @@ -0,0 +1,64 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a cauldron + */ +public class Cauldron extends MaterialData { + private static final int CAULDRON_FULL = 3; + private static final int CAULDRON_EMPTY = 0; + + public Cauldron() { + super(Material.CAULDRON); + } + + /** + * + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cauldron(int type, byte data) { + super(type, data); + } + + /** + * + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cauldron(byte data) { + super(Material.CAULDRON, data); + } + + /** + * Check if the cauldron is full. + * + * @return True if it is full. + */ + public boolean isFull() { + return getData() >= CAULDRON_FULL; + } + + /** + * Check if the cauldron is empty. + * + * @return True if it is empty. + */ + public boolean isEmpty() { + return getData() <= CAULDRON_EMPTY; + } + + @Override + public String toString() { + return (isEmpty() ? "EMPTY" : (isFull() ? "FULL" : getData() + "/3 FULL")) + " CAULDRON"; + } + + @Override + public Cauldron clone() { + return (Cauldron) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Chest.java b/src/main/java/org/bukkit/material/Chest.java new file mode 100644 index 00000000..0db8aa54 --- /dev/null +++ b/src/main/java/org/bukkit/material/Chest.java @@ -0,0 +1,62 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a chest + */ +public class Chest extends DirectionalContainer { + + public Chest() { + super(Material.CHEST); + } + + /** + * Instantiate a chest facing in a particular direction. + * + * @param direction the direction the chest's lit opens towards + */ + public Chest(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Chest(final int type) { + super(type); + } + + public Chest(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Chest(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Chest(final Material type, final byte data) { + super(type, data); + } + + @Override + public Chest clone() { + return (Chest) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Coal.java b/src/main/java/org/bukkit/material/Coal.java new file mode 100644 index 00000000..dd940b63 --- /dev/null +++ b/src/main/java/org/bukkit/material/Coal.java @@ -0,0 +1,79 @@ +package org.bukkit.material; + +import org.bukkit.CoalType; +import org.bukkit.Material; + +/** + * Represents the different types of coals. + */ +public class Coal extends MaterialData { + public Coal() { + super(Material.COAL); + } + + public Coal(CoalType type) { + this(); + setType(type); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Coal(final int type) { + super(type); + } + + public Coal(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Coal(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Coal(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current type of this coal + * + * @return CoalType of this coal + */ + public CoalType getType() { + return CoalType.getByData(getData()); + } + + /** + * Sets the type of this coal + * + * @param type New type of this coal + */ + public void setType(CoalType type) { + setData(type.getData()); + } + + @Override + public String toString() { + return getType() + " " + super.toString(); + } + + @Override + public Coal clone() { + return (Coal) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/CocoaPlant.java b/src/main/java/org/bukkit/material/CocoaPlant.java new file mode 100644 index 00000000..6dede93c --- /dev/null +++ b/src/main/java/org/bukkit/material/CocoaPlant.java @@ -0,0 +1,133 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents the cocoa plant + */ +public class CocoaPlant extends MaterialData implements Directional, Attachable { + + public enum CocoaPlantSize { + SMALL, + MEDIUM, + LARGE + } + + public CocoaPlant() { + super(Material.COCOA); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public CocoaPlant(final int type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public CocoaPlant(final int type, final byte data) { + super(type, data); + } + + public CocoaPlant(CocoaPlantSize sz) { + this(); + setSize(sz); + } + + public CocoaPlant(CocoaPlantSize sz, BlockFace dir) { + this(); + setSize(sz); + setFacingDirection(dir); + } + + /** + * Get size of plant + * + * @return size + */ + public CocoaPlantSize getSize() { + switch (getData() & 0xC) { + case 0: + return CocoaPlantSize.SMALL; + case 4: + return CocoaPlantSize.MEDIUM; + default: + return CocoaPlantSize.LARGE; + } + } + + /** + * Set size of plant + * + * @param sz - size of plant + */ + public void setSize(CocoaPlantSize sz) { + int dat = getData() & 0x3; + switch (sz) { + case SMALL: + break; + case MEDIUM: + dat |= 0x4; + break; + case LARGE: + dat |= 0x8; + break; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + return getFacing().getOppositeFace(); + } + + public void setFacingDirection(BlockFace face) { + int dat = getData() & 0xC; + switch (face) { + default: + case SOUTH: + break; + case WEST: + dat |= 0x1; + break; + case NORTH: + dat |= 0x2; + break; + case EAST: + dat |= 0x3; + break; + } + setData((byte) dat); + } + + public BlockFace getFacing() { + switch (getData() & 0x3) { + case 0: + return BlockFace.SOUTH; + case 1: + return BlockFace.WEST; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.EAST; + } + return null; + } + + @Override + public CocoaPlant clone() { + return (CocoaPlant) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + getSize(); + } +} diff --git a/src/main/java/org/bukkit/material/Colorable.java b/src/main/java/org/bukkit/material/Colorable.java new file mode 100644 index 00000000..3b91b247 --- /dev/null +++ b/src/main/java/org/bukkit/material/Colorable.java @@ -0,0 +1,24 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; + +/** + * An object that can be colored. + */ +public interface Colorable { + + /** + * Gets the color of this object. + * + * @return The DyeColor of this object. + */ + public DyeColor getColor(); + + /** + * Sets the color of this object to the specified DyeColor. + * + * @param color The color of the object, as a DyeColor. + */ + public void setColor(DyeColor color); + +} diff --git a/src/main/java/org/bukkit/material/Command.java b/src/main/java/org/bukkit/material/Command.java new file mode 100644 index 00000000..b484229e --- /dev/null +++ b/src/main/java/org/bukkit/material/Command.java @@ -0,0 +1,75 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a command block + */ +public class Command extends MaterialData implements Redstone { + public Command() { + super(Material.COMMAND); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Command(final int type) { + super(type); + } + + public Command(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Command(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Command(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 1) != 0; + } + + /** + * Sets the current state of this Material + * + * @param bool + * whether or not the command block is powered + */ + public void setPowered(boolean bool) { + setData((byte) (bool ? (getData() | 1) : (getData() & -2))); + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Command clone() { + return (Command) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Comparator.java b/src/main/java/org/bukkit/material/Comparator.java new file mode 100644 index 00000000..b5b44b65 --- /dev/null +++ b/src/main/java/org/bukkit/material/Comparator.java @@ -0,0 +1,200 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a comparator in the on or off state, in normal or subtraction mode and facing in a specific direction. + * + * @see Material#REDSTONE_COMPARATOR_OFF + * @see Material#REDSTONE_COMPARATOR_ON + */ +public class Comparator extends MaterialData implements Directional, Redstone { + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.NORTH; + protected static final boolean DEFAULT_SUBTRACTION_MODE = false; + protected static final boolean DEFAULT_STATE = false; + + /** + * Constructs a comparator switched off, with the default mode (normal) and facing the default direction (north). + */ + public Comparator() { + this(DEFAULT_DIRECTION, DEFAULT_SUBTRACTION_MODE, false); + } + + /** + * Constructs a comparator switched off, with the default mode (normal) and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection) { + this(facingDirection, DEFAULT_SUBTRACTION_MODE, DEFAULT_STATE); + } + + /** + * Constructs a comparator switched off, with the specified mode and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection, boolean isSubtraction) { + this(facingDirection, isSubtraction, DEFAULT_STATE); + } + + /** + * Constructs a comparator switched on or off, with the specified mode and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + * @param state True if the comparator is in the on state + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection, boolean isSubtraction, boolean state) { + super(state ? Material.REDSTONE_COMPARATOR_ON : Material.REDSTONE_COMPARATOR_OFF); + setFacingDirection(facingDirection); + setSubtractionMode(isSubtraction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Comparator(int type) { + super(type); + } + + public Comparator(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Comparator(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Comparator(Material type, byte data) { + super(type, data); + } + + /** + * Sets whether the comparator is in subtraction mode. + * + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + */ + public void setSubtractionMode(boolean isSubtraction) { + setData((byte) (getData() & 0xB | (isSubtraction ? 0x4 : 0x0))); + } + + /** + * Checks whether the comparator is in subtraction mode + * + * @return True if the comparator is in subtraction mode, false if normal comparator operation + */ + public boolean isSubtractionMode() { + return (getData() & 0x4) != 0; + } + + /** + * Sets the direction this comparator is facing + * + * @param face The direction to set this comparator to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int data = getData() & 0xC; + + switch (face) { + case EAST: + data |= 0x1; + break; + + case SOUTH: + data |= 0x2; + break; + + case WEST: + data |= 0x3; + break; + + case NORTH: + default: + data |= 0x0; + } + + setData((byte) data); + } + + /** + * Gets the direction this comparator is facing + * + * @return The direction this comparator is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + default: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " in " + (isSubtractionMode() ? "subtraction" : "comparator") + " mode"; + } + + @Override + public Comparator clone() { + return (Comparator) super.clone(); + } + + /** + * Checks if the comparator is powered + * + * @return true if the comparator is powered + */ + @Override + public boolean isPowered() { + return getItemType() == Material.REDSTONE_COMPARATOR_ON; + } + + /** + * Checks if the comparator is being powered + * + * @return true if the comparator is being powered + */ + public boolean isBeingPowered() { + return (getData() & 0x8) != 0; + } +} diff --git a/src/main/java/org/bukkit/material/Crops.java b/src/main/java/org/bukkit/material/Crops.java new file mode 100644 index 00000000..f149831c --- /dev/null +++ b/src/main/java/org/bukkit/material/Crops.java @@ -0,0 +1,151 @@ +package org.bukkit.material; + +import org.bukkit.CropState; +import org.bukkit.Material; + +/** + * Represents the different types of crops in different states of growth. + * + * @see Material#CROPS + * @see Material#CARROT + * @see Material#POTATO + * @see Material#BEETROOT_BLOCK + * @see Material#NETHER_WARTS + */ +public class Crops extends MaterialData { + protected static final Material DEFAULT_TYPE = Material.CROPS; + protected static final CropState DEFAULT_STATE = CropState.SEEDED; + + /** + * Constructs a wheat crop block in the seeded state. + */ + public Crops() { + this(DEFAULT_TYPE, DEFAULT_STATE); + } + + /** + * Constructs a wheat crop block in the given growth state + * + * @param state The growth state of the crops + */ + public Crops(CropState state) { + this(DEFAULT_TYPE, state); + setState(state); + } + + /** + * Constructs a crop block of the given type and in the given growth state + * + * @param type The type of crops + * @param state The growth state of the crops + */ + public Crops(final Material type, final CropState state) { + super(type); + setState(state); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Crops(final int type) { + super(type); + } + + /** + * Constructs a crop block of the given type and in the seeded state + * + * @param type The type of crops + */ + public Crops(final Material type) { + this(type, DEFAULT_STATE); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Crops(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Crops(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current growth state of this crop + * + * For crops with only four growth states such as beetroot, only the values SEEDED, SMALL, TALL and RIPE will be + * returned. + * + * @return CropState of this crop + */ + public CropState getState() { + switch (getItemType()) { + case CROPS: + case CARROT: + case POTATO: + // Mask the data just in case top bit set + return CropState.getByData((byte) (getData() & 0x7)); + case BEETROOT_BLOCK: + case NETHER_WARTS: + // Mask the data just in case top bits are set + // Will return SEEDED, SMALL, TALL, RIPE for the three growth data values + return CropState.getByData((byte) (((getData() & 0x3) * 7 + 2) / 3)); + default: + throw new IllegalArgumentException("Block type is not a crop"); + } + } + + /** + * Sets the growth state of this crop + * + * For crops with only four growth states such as beetroot, the 8 CropStates are mapped into four states: + * + * SEEDED, SMALL, TALL and RIPE + * + * GERMINATED will change to SEEDED + * VERY_SMALL will change to SMALL + * MEDIUM will change to TALL + * VERY_TALL will change to RIPE + * + * @param state New growth state of this crop + */ + public void setState(CropState state) { + switch (getItemType()) { + case CROPS: + case CARROT: + case POTATO: + // Preserve the top bit in case it is set + setData((byte) ((getData() & 0x8) | state.getData())); + break; + case NETHER_WARTS: + case BEETROOT_BLOCK: + // Preserve the top bits in case they are set + setData((byte) ((getData() & 0xC) | (state.getData() >> 1))); + break; + default: + throw new IllegalArgumentException("Block type is not a crop"); + } + } + + @Override + public String toString() { + return getState() + " " + super.toString(); + } + + @Override + public Crops clone() { + return (Crops) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/DetectorRail.java b/src/main/java/org/bukkit/material/DetectorRail.java new file mode 100644 index 00000000..652a4b5e --- /dev/null +++ b/src/main/java/org/bukkit/material/DetectorRail.java @@ -0,0 +1,58 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a detector rail + */ +public class DetectorRail extends ExtendedRails implements PressureSensor { + public DetectorRail() { + super(Material.DETECTOR_RAIL); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public DetectorRail(final int type) { + super(type); + } + + public DetectorRail(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DetectorRail(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DetectorRail(final Material type, final byte data) { + super(type, data); + } + + public boolean isPressed() { + return (getData() & 0x8) == 0x8; + } + + public void setPressed(boolean isPressed) { + setData((byte) (isPressed ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public DetectorRail clone() { + return (DetectorRail) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Diode.java b/src/main/java/org/bukkit/material/Diode.java new file mode 100644 index 00000000..84014c4f --- /dev/null +++ b/src/main/java/org/bukkit/material/Diode.java @@ -0,0 +1,209 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a diode/repeater in the on or off state, with a delay and facing + * in a specific direction. + * + * @see Material#DIODE_BLOCK_OFF + * @see Material#DIODE_BLOCK_ON + */ +public class Diode extends MaterialData implements Directional, Redstone { + + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.NORTH; + protected static final int DEFAULT_DELAY = 1; + protected static final boolean DEFAULT_STATE = false; + + /** + * Constructs a diode switched on, with a delay of 1 and facing the default + * direction (north). + * + * By default this constructor creates a diode that is switched on for + * backwards compatibility with past implementations. + */ + public Diode() { + this(DEFAULT_DIRECTION, DEFAULT_DELAY, true); + } + + /** + * Constructs a diode switched off, with a delay of 1 and facing the + * specified direction. + * + * @param facingDirection the direction the diode is facing + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection) { + this(facingDirection, DEFAULT_DELAY, DEFAULT_STATE); + } + + /** + * Constructs a diode switched off, with the specified delay and facing the + * specified direction. + * + * @param facingDirection the direction the diode is facing + * @param delay The number of ticks (1-4) before the diode turns on after + * being powered + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection, int delay) { + this(facingDirection, delay, DEFAULT_STATE); + } + + /** + * Constructs a diode switched on or off, with the specified delay and + * facing the specified direction. + * + * @param facingDirection the direction the diode is facing + * @param delay The number of ticks (1-4) before the diode turns on after + * being powered + * @param state True if the diode is in the on state + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection, int delay, boolean state) { + super(state ? Material.DIODE_BLOCK_ON : Material.DIODE_BLOCK_OFF); + setFacingDirection(facingDirection); + setDelay(delay); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Diode(int type) { + super(type); + } + + public Diode(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Diode(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Diode(Material type, byte data) { + super(type, data); + } + + /** + * Sets the delay of the repeater. + * + * @param delay The new delay (1-4) + */ + public void setDelay(int delay) { + if (delay > 4) { + delay = 4; + } + if (delay < 1) { + delay = 1; + } + byte newData = (byte) (getData() & 0x3); + + setData((byte) (newData | ((delay - 1) << 2))); + } + + /** + * Gets the delay of the repeater in ticks. + * + * @return The delay (1-4) + */ + public int getDelay() { + return (getData() >> 2) + 1; + } + + /** + * Sets the direction this diode is facing. + * + * @param face The direction to set this diode to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int delay = getDelay(); + byte data; + + switch (face) { + case EAST: + data = 0x1; + break; + case SOUTH: + data = 0x2; + break; + case WEST: + data = 0x3; + break; + case NORTH: + default: + data = 0x0; + } + + setData(data); + setDelay(delay); + } + + /** + * Gets the direction this diode is facing + * + * @return The direction this diode is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + default: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " with " + getDelay() + " ticks delay"; + } + + @Override + public Diode clone() { + return (Diode) super.clone(); + } + + /** + * Checks if the diode is powered. + * + * @return true if the diode is powered + */ + @Override + public boolean isPowered() { + return getItemType() == Material.DIODE_BLOCK_ON; + } +} diff --git a/src/main/java/org/bukkit/material/Directional.java b/src/main/java/org/bukkit/material/Directional.java new file mode 100644 index 00000000..25624d26 --- /dev/null +++ b/src/main/java/org/bukkit/material/Directional.java @@ -0,0 +1,20 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; + +public interface Directional { + + /** + * Sets the direction that this block is facing in + * + * @param face The facing direction + */ + public void setFacingDirection(BlockFace face); + + /** + * Gets the direction this block is facing + * + * @return the direction this block is facing + */ + public BlockFace getFacing(); +} diff --git a/src/main/java/org/bukkit/material/DirectionalContainer.java b/src/main/java/org/bukkit/material/DirectionalContainer.java new file mode 100644 index 00000000..b56f0988 --- /dev/null +++ b/src/main/java/org/bukkit/material/DirectionalContainer.java @@ -0,0 +1,95 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a furnace or a dispenser. + */ +public class DirectionalContainer extends MaterialData implements Directional { + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public DirectionalContainer(final int type) { + super(type); + } + + public DirectionalContainer(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DirectionalContainer(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DirectionalContainer(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + + setData(data); + } + + public BlockFace getFacing() { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public DirectionalContainer clone() { + return (DirectionalContainer) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Dispenser.java b/src/main/java/org/bukkit/material/Dispenser.java new file mode 100644 index 00000000..988407cf --- /dev/null +++ b/src/main/java/org/bukkit/material/Dispenser.java @@ -0,0 +1,114 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a dispenser. + */ +public class Dispenser extends FurnaceAndDispenser { + + public Dispenser() { + super(Material.DISPENSER); + } + + public Dispenser(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Dispenser(final int type) { + super(type); + } + + public Dispenser(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dispenser(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dispenser(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case DOWN: + data = 0x0; + break; + + case UP: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + + setData(data); + } + + public BlockFace getFacing() { + int data = getData() & 0x7; + + switch (data) { + case 0x0: + return BlockFace.DOWN; + + case 0x1: + return BlockFace.UP; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + default: + return BlockFace.EAST; + } + } + + @Override + public Dispenser clone() { + return (Dispenser) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Door.java b/src/main/java/org/bukkit/material/Door.java new file mode 100644 index 00000000..07a40a00 --- /dev/null +++ b/src/main/java/org/bukkit/material/Door.java @@ -0,0 +1,338 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; +import org.bukkit.block.BlockFace; + +/** + * Represents a door. + * + * This class was previously deprecated, but has been retrofitted to + * work with modern doors. Some methods are undefined dependant on isTopHalf() + * due to Minecraft's internal representation of doors. + * + * @see Material#WOODEN_DOOR + * @see Material#IRON_DOOR_BLOCK + * @see Material#SPRUCE_DOOR + * @see Material#BIRCH_DOOR + * @see Material#JUNGLE_DOOR + * @see Material#ACACIA_DOOR + * @see Material#DARK_OAK_DOOR + */ +public class Door extends MaterialData implements Directional, Openable { + + // This class breaks API contracts on Directional and Openable because + // of the way doors are currently implemented. Beware! + + /** + * @deprecated Artifact of old API, equivalent to new Door(Material.WOODEN_DOOR); + */ + @Deprecated + public Door() { + super(Material.WOODEN_DOOR); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Door(final int type) { + super(type); + } + + public Door(final Material type) { + super(type); + } + + /** + * Constructs the bottom half of a door of the given material type, facing the specified direction and set to closed + * + * @param type The type of material this door is made of. This must match the type of the block above. + * @param face The direction the door is facing. + * + * @see Material#WOODEN_DOOR + * @see Material#IRON_DOOR_BLOCK + * @see Material#SPRUCE_DOOR + * @see Material#BIRCH_DOOR + * @see Material#JUNGLE_DOOR + * @see Material#ACACIA_DOOR + * @see Material#DARK_OAK_DOOR + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final Material type, BlockFace face) { + this(type, face, false); + } + + /** + * Constructs the bottom half of a door of the given material type, facing the specified direction and set to open + * or closed + * + * @param type The type of material this door is made of. This must match the type of the block above. + * @param face The direction the door is facing. + * @param isOpen Whether the door is currently opened. + * + * @see Material#WOODEN_DOOR + * @see Material#IRON_DOOR_BLOCK + * @see Material#SPRUCE_DOOR + * @see Material#BIRCH_DOOR + * @see Material#JUNGLE_DOOR + * @see Material#ACACIA_DOOR + * @see Material#DARK_OAK_DOOR + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final Material type, BlockFace face, boolean isOpen) { + super(type); + setTopHalf(false); + setFacingDirection(face); + setOpen(isOpen); + } + + /** + * Constructs the top half of door of the given material type and with the hinge on the left or right + * + * @param type The type of material this door is made of. This must match the type of the block below. + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + * + * @see Material#WOODEN_DOOR + * @see Material#IRON_DOOR_BLOCK + * @see Material#SPRUCE_DOOR + * @see Material#BIRCH_DOOR + * @see Material#JUNGLE_DOOR + * @see Material#ACACIA_DOOR + * @see Material#DARK_OAK_DOOR + */ + public Door(final Material type, boolean isHingeRight) { + super(type); + setTopHalf(true); + setHinge(isHingeRight); + } + + /** + * Constructs the bottom half of a wooden door of the given species, facing the specified direction and set to + * closed + * + * @param species The species this wooden door is made of. This must match the species of the block above. + * @param face The direction the door is facing. + * + * @see TreeSpecies + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final TreeSpecies species, BlockFace face) { + this(getWoodDoorOfSpecies(species), face, false); + } + + /** + * Constructs the bottom half of a wooden door of the given species, facing the specified direction and set to open + * or closed + * + * @param species The species this wooden door is made of. This must match the species of the block above. + * @param face The direction the door is facing. + * @param isOpen Whether the door is currently opened. + * + * @see TreeSpecies + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final TreeSpecies species, BlockFace face, boolean isOpen) { + this(getWoodDoorOfSpecies(species), face, isOpen); + } + + /** + * Constructs the top half of a wooden door of the given species and with the hinge on the left or right + * + * @param species The species this wooden door is made of. This must match the species of the block below. + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + * + * @see TreeSpecies + */ + public Door(final TreeSpecies species, boolean isHingeRight) { + this(getWoodDoorOfSpecies(species), isHingeRight); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Door(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Door(final Material type, final byte data) { + super(type, data); + } + + /** + * Returns the item type of a wooden door for the given tree species. + * + * @param species The species of wood door required. + * @return The item type for the given species. + * + * @see Material#WOODEN_DOOR + * @see Material#SPRUCE_DOOR + * @see Material#BIRCH_DOOR + * @see Material#JUNGLE_DOOR + * @see Material#ACACIA_DOOR + * @see Material#DARK_OAK_DOOR + */ + public static Material getWoodDoorOfSpecies(TreeSpecies species) { + switch (species) { + default: + case GENERIC: + return Material.WOODEN_DOOR; + case BIRCH: + return Material.BIRCH_DOOR; + case REDWOOD: + return Material.SPRUCE_DOOR; + case JUNGLE: + return Material.JUNGLE_DOOR; + case ACACIA: + return Material.ACACIA_DOOR; + case DARK_OAK: + return Material.DARK_OAK_DOOR; + } + } + + /** + * Result is undefined if isTopHalf() is true. + */ + public boolean isOpen() { + return ((getData() & 0x4) == 0x4); + } + + /** + * Set whether the door is open. Undefined if isTopHalf() is true. + */ + public void setOpen(boolean isOpen) { + setData((byte) (isOpen ? (getData() | 0x4) : (getData() & ~0x4))); + } + + /** + * @return whether this is the top half of the door + */ + public boolean isTopHalf() { + return ((getData() & 0x8) == 0x8); + } + + /** + * Configure this part of the door to be either the top or the bottom half + * + * @param isTopHalf True to make it the top half. + */ + public void setTopHalf(boolean isTopHalf) { + setData((byte) (isTopHalf ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * @return BlockFace.SELF + * @deprecated This method should not be used; use hinge and facing accessors instead. + */ + @Deprecated + public BlockFace getHingeCorner() { + return BlockFace.SELF; + } + + @Override + public String toString() { + return (isTopHalf() ? "TOP" : "BOTTOM") + " half of " + super.toString(); + } + + /** + * Set the direction that this door should is facing. + * + * Undefined if isTopHalf() is true. + * + * @param face the direction + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0xC); + switch (face) { + case WEST: + data |= 0x0; + break; + case NORTH: + data |= 0x1; + break; + case EAST: + data |= 0x2; + break; + case SOUTH: + data |= 0x3; + break; + } + setData(data); + } + + /** + * Get the direction that this door is facing. + * + * Undefined if isTopHalf() is true. + * + * @return the direction + */ + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + switch (data) { + case 0: + return BlockFace.WEST; + case 1: + return BlockFace.NORTH; + case 2: + return BlockFace.EAST; + case 3: + return BlockFace.SOUTH; + default: + throw new IllegalStateException("Unknown door facing (data: " + data + ")"); + } + } + + /** + * Returns the side of the door the hinge is on. + * + * Undefined if isTopHalf() is false. + * + * @return false for left hinge, true for right hinge + */ + public boolean getHinge() { + return (getData() & 0x1) == 1; + } + + /** + * Set whether the hinge is on the left or right side. Left is false, right is true. + * + * Undefined if isTopHalf() is false. + * + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + */ + public void setHinge(boolean isHingeRight) { + setData((byte) (isHingeRight ? (getData() | 0x1) : (getData() & ~0x1))); + } + + @Override + public Door clone() { + return (Door) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Dye.java b/src/main/java/org/bukkit/material/Dye.java new file mode 100644 index 00000000..7174fdbf --- /dev/null +++ b/src/main/java/org/bukkit/material/Dye.java @@ -0,0 +1,81 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; +import org.bukkit.Material; + +/** + * Represents dye + */ +public class Dye extends MaterialData implements Colorable { + public Dye() { + super(Material.INK_SACK); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Dye(final int type) { + super(type); + } + + public Dye(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dye(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dye(final Material type, final byte data) { + super(type, data); + } + + /** + * @param color color of the dye + */ + public Dye(final DyeColor color) { + super(Material.INK_SACK, color.getDyeData()); + } + + /** + * Gets the current color of this dye + * + * @return DyeColor of this dye + */ + public DyeColor getColor() { + return DyeColor.getByDyeData(getData()); + } + + /** + * Sets the color of this dye + * + * @param color New color of this dye + */ + public void setColor(DyeColor color) { + setData(color.getDyeData()); + } + + @Override + public String toString() { + return getColor() + " DYE(" + getData() + ")"; + } + + @Override + public Dye clone() { + return (Dye) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/EnderChest.java b/src/main/java/org/bukkit/material/EnderChest.java new file mode 100644 index 00000000..d3a60196 --- /dev/null +++ b/src/main/java/org/bukkit/material/EnderChest.java @@ -0,0 +1,62 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents an ender chest + */ +public class EnderChest extends DirectionalContainer { + + public EnderChest() { + super(Material.ENDER_CHEST); + } + + /** + * Instantiate an ender chest facing in a particular direction. + * + * @param direction the direction the ender chest's lid opens towards + */ + public EnderChest(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public EnderChest(final int type) { + super(type); + } + + public EnderChest(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public EnderChest(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public EnderChest(final Material type, final byte data) { + super(type, data); + } + + @Override + public EnderChest clone() { + return (EnderChest) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/ExtendedRails.java b/src/main/java/org/bukkit/material/ExtendedRails.java new file mode 100644 index 00000000..34fb55e1 --- /dev/null +++ b/src/main/java/org/bukkit/material/ExtendedRails.java @@ -0,0 +1,75 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * This is the superclass for the {@link DetectorRail} and {@link PoweredRail} + * classes + */ +public class ExtendedRails extends Rails { + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public ExtendedRails(final int type) { + super(type); + } + + public ExtendedRails(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public ExtendedRails(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public ExtendedRails(final Material type, final byte data) { + super(type, data); + } + + @Override + public boolean isCurve() { + return false; + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected byte getConvertedData() { + return (byte) (getData() & 0x7); + } + + @Override + public void setDirection(BlockFace face, boolean isOnSlope) { + boolean extraBitSet = (getData() & 0x8) == 0x8; + + if (face != BlockFace.WEST && face != BlockFace.EAST && face != BlockFace.NORTH && face != BlockFace.SOUTH) { + throw new IllegalArgumentException("Detector rails and powered rails cannot be set on a curve!"); + } + + super.setDirection(face, isOnSlope); + setData((byte) (extraBitSet ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public ExtendedRails clone() { + return (ExtendedRails) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/FlowerPot.java b/src/main/java/org/bukkit/material/FlowerPot.java new file mode 100644 index 00000000..a3951e7a --- /dev/null +++ b/src/main/java/org/bukkit/material/FlowerPot.java @@ -0,0 +1,141 @@ +package org.bukkit.material; + +import org.bukkit.GrassSpecies; +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents a flower pot. + * + * @deprecated Flower pots are now tile entities, use + * {@link org.bukkit.block.FlowerPot}. + */ +@Deprecated +public class FlowerPot extends MaterialData { + + /** + * Default constructor for a flower pot. + */ + public FlowerPot() { + super(Material.FLOWER_POT); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public FlowerPot(final int type) { + super(type); + } + + public FlowerPot(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FlowerPot(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FlowerPot(final Material type, final byte data) { + super(type, data); + } + + /** + * Get the material in the flower pot + * + * @return material MaterialData for the block currently in the flower pot + * or null if empty + */ + public MaterialData getContents() { + switch (getData()) { + case 1: + return new MaterialData(Material.RED_ROSE); + case 2: + return new MaterialData(Material.YELLOW_FLOWER); + case 3: + return new Tree(TreeSpecies.GENERIC); + case 4: + return new Tree(TreeSpecies.REDWOOD); + case 5: + return new Tree(TreeSpecies.BIRCH); + case 6: + return new Tree(TreeSpecies.JUNGLE); + case 7: + return new MaterialData(Material.RED_MUSHROOM); + case 8: + return new MaterialData(Material.BROWN_MUSHROOM); + case 9: + return new MaterialData(Material.CACTUS); + case 10: + return new MaterialData(Material.DEAD_BUSH); + case 11: + return new LongGrass(GrassSpecies.FERN_LIKE); + default: + return null; + } + } + + /** + * Set the contents of the flower pot + * + * @param materialData MaterialData of the block to put in the flower pot. + */ + public void setContents(MaterialData materialData) { + Material mat = materialData.getItemType(); + + if (mat == Material.RED_ROSE) { + setData((byte) 1); + } else if (mat == Material.YELLOW_FLOWER) { + setData((byte) 2); + } else if (mat == Material.RED_MUSHROOM) { + setData((byte) 7); + } else if (mat == Material.BROWN_MUSHROOM) { + setData((byte) 8); + } else if (mat == Material.CACTUS) { + setData((byte) 9); + } else if (mat == Material.DEAD_BUSH) { + setData((byte) 10); + } else if (mat == Material.SAPLING) { + TreeSpecies species = ((Tree) materialData).getSpecies(); + + if (species == TreeSpecies.GENERIC) { + setData((byte) 3); + } else if (species == TreeSpecies.REDWOOD) { + setData((byte) 4); + } else if (species == TreeSpecies.BIRCH) { + setData((byte) 5); + } else { + setData((byte) 6); + } + } else if (mat == Material.LONG_GRASS) { + GrassSpecies species = ((LongGrass) materialData).getSpecies(); + + if (species == GrassSpecies.FERN_LIKE) { + setData((byte) 11); + } + } + } + + @Override + public String toString() { + return super.toString() + " containing " + getContents(); + } + + @Override + public FlowerPot clone() { + return (FlowerPot) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Furnace.java b/src/main/java/org/bukkit/material/Furnace.java new file mode 100644 index 00000000..c607226f --- /dev/null +++ b/src/main/java/org/bukkit/material/Furnace.java @@ -0,0 +1,62 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a furnace. + */ +public class Furnace extends FurnaceAndDispenser { + + public Furnace() { + super(Material.FURNACE); + } + + /** + * Instantiate a furnace facing in a particular direction. + * + * @param direction the direction the furnace's "opening" is facing + */ + public Furnace(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Furnace(final int type) { + super(type); + } + + public Furnace(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Furnace(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Furnace(final Material type, final byte data) { + super(type, data); + } + + @Override + public Furnace clone() { + return (Furnace) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/FurnaceAndDispenser.java b/src/main/java/org/bukkit/material/FurnaceAndDispenser.java new file mode 100644 index 00000000..184fda21 --- /dev/null +++ b/src/main/java/org/bukkit/material/FurnaceAndDispenser.java @@ -0,0 +1,47 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a furnace or dispenser, two types of directional containers + */ +public class FurnaceAndDispenser extends DirectionalContainer { + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public FurnaceAndDispenser(final int type) { + super(type); + } + + public FurnaceAndDispenser(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FurnaceAndDispenser(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FurnaceAndDispenser(final Material type, final byte data) { + super(type, data); + } + + @Override + public FurnaceAndDispenser clone() { + return (FurnaceAndDispenser) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Gate.java b/src/main/java/org/bukkit/material/Gate.java new file mode 100644 index 00000000..19a66ee9 --- /dev/null +++ b/src/main/java/org/bukkit/material/Gate.java @@ -0,0 +1,91 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a fence gate + */ +public class Gate extends MaterialData implements Directional, Openable { + private static final byte OPEN_BIT = 0x4; + private static final byte DIR_BIT = 0x3; + private static final byte GATE_SOUTH = 0x0; + private static final byte GATE_WEST = 0x1; + private static final byte GATE_NORTH = 0x2; + private static final byte GATE_EAST = 0x3; + + public Gate() { + super(Material.FENCE_GATE); + } + + public Gate(int type, byte data) { + super(type, data); + } + + public Gate(byte data) { + super(Material.FENCE_GATE, data); + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & ~DIR_BIT); + + switch (face) { + default: + case EAST: + data |= GATE_SOUTH; + break; + case SOUTH: + data |= GATE_WEST; + break; + case WEST: + data |= GATE_NORTH; + break; + case NORTH: + data |= GATE_EAST; + break; + } + + setData(data); + } + + public BlockFace getFacing() { + switch (getData() & DIR_BIT) { + case GATE_SOUTH: + return BlockFace.EAST; + case GATE_WEST: + return BlockFace.SOUTH; + case GATE_NORTH: + return BlockFace.WEST; + case GATE_EAST: + return BlockFace.NORTH; + } + + return BlockFace.EAST; + } + + public boolean isOpen() { + return (getData() & OPEN_BIT) > 0; + } + + public void setOpen(boolean isOpen) { + byte data = getData(); + + if (isOpen) { + data |= OPEN_BIT; + } else { + data &= ~OPEN_BIT; + } + + setData(data); + } + + @Override + public String toString() { + return (isOpen() ? "OPEN " : "CLOSED ") + " facing and opening " + getFacing(); + } + + @Override + public Gate clone() { + return (Gate) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Hopper.java b/src/main/java/org/bukkit/material/Hopper.java new file mode 100644 index 00000000..d1516f25 --- /dev/null +++ b/src/main/java/org/bukkit/material/Hopper.java @@ -0,0 +1,181 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a hopper in an active or deactivated state and facing in a + * specific direction. + * + * @see Material#HOPPER + */ +public class Hopper extends MaterialData implements Directional, Redstone { + + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.DOWN; + protected static final boolean DEFAULT_ACTIVE = true; + + /** + * Constructs a hopper facing the default direction (down) and initially + * active. + */ + public Hopper() { + this(DEFAULT_DIRECTION, DEFAULT_ACTIVE); + } + + /** + * Constructs a hopper facing the specified direction and initially active. + * + * @param facingDirection the direction the hopper is facing + * + * @see BlockFace + */ + public Hopper(BlockFace facingDirection) { + this(facingDirection, DEFAULT_ACTIVE); + } + + /** + * Constructs a hopper facing the specified direction and either active or + * not. + * + * @param facingDirection the direction the hopper is facing + * @param isActive True if the hopper is initially active, false if + * deactivated + * + * @see BlockFace + */ + public Hopper(BlockFace facingDirection, boolean isActive) { + super(Material.HOPPER); + setFacingDirection(facingDirection); + setActive(isActive); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Hopper(int type) { + super(type); + } + + public Hopper(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Hopper(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Hopper(Material type, byte data) { + super(type, data); + } + + /** + * Sets whether the hopper is active or not. + * + * @param isActive True if the hopper is active, false if deactivated as if + * powered by redstone + */ + public void setActive(boolean isActive) { + setData((byte) (getData() & 0x7 | (isActive ? 0x0 : 0x8))); + } + + /** + * Checks whether the hopper is active or not. + * + * @return True if the hopper is active, false if deactivated + */ + public boolean isActive() { + return (getData() & 0x8) == 0; + } + + /** + * Sets the direction this hopper is facing + * + * @param face The direction to set this hopper to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int data = getData() & 0x8; + + switch (face) { + case DOWN: + data |= 0x0; + break; + case NORTH: + data |= 0x2; + break; + case SOUTH: + data |= 0x3; + break; + case WEST: + data |= 0x4; + break; + case EAST: + data |= 0x5; + break; + } + + setData((byte) data); + } + + /** + * Gets the direction this hopper is facing + * + * @return The direction this hopper is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + default: + case 0x0: + return BlockFace.DOWN; + case 0x2: + return BlockFace.NORTH; + case 0x3: + return BlockFace.SOUTH; + case 0x4: + return BlockFace.WEST; + case 0x5: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Hopper clone() { + return (Hopper) super.clone(); + } + + /** + * Checks if the hopper is powered. + * + * @return true if the hopper is powered + */ + @Override + public boolean isPowered() { + return (getData() & 0x8) != 0; + } +} diff --git a/src/main/java/org/bukkit/material/Ladder.java b/src/main/java/org/bukkit/material/Ladder.java new file mode 100644 index 00000000..cd4d6911 --- /dev/null +++ b/src/main/java/org/bukkit/material/Ladder.java @@ -0,0 +1,104 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents Ladder data + */ +public class Ladder extends SimpleAttachableMaterialData { + public Ladder() { + super(Material.LADDER); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Ladder(final int type) { + super(type); + } + + public Ladder(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Ladder(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Ladder(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } + + /** + * Sets the direction this ladder is facing + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) 0x0; + + switch (face) { + case SOUTH: + data = 0x2; + break; + + case NORTH: + data = 0x3; + break; + + case EAST: + data = 0x4; + break; + + case WEST: + data = 0x5; + break; + } + + setData(data); + + } + + @Override + public Ladder clone() { + return (Ladder) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Leaves.java b/src/main/java/org/bukkit/material/Leaves.java new file mode 100644 index 00000000..904659b1 --- /dev/null +++ b/src/main/java/org/bukkit/material/Leaves.java @@ -0,0 +1,156 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of leaf block that may be permanent or can + * decay when too far from a log. + * + * @see Material#LEAVES + * @see Material#LEAVES_2 + */ +public class Leaves extends Wood { + protected static final Material DEFAULT_TYPE = Material.LEAVES; + protected static final boolean DEFAULT_DECAYABLE = true; + + /** + * Constructs a leaf block. + */ + public Leaves() { + this(DEFAULT_TYPE, DEFAULT_SPECIES, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given tree species. + * + * @param species the species of the wood block + */ + public Leaves(TreeSpecies species) { + this(DEFAULT_TYPE, species, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given tree species and flag for whether + * this leaf block will disappear when too far from a log. + * + * @param species the species of the wood block + * @param isDecayable whether the block is permanent or can disappear + */ + public Leaves(TreeSpecies species, boolean isDecayable) { + this(DEFAULT_TYPE, species, isDecayable); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Leaves(final int type) { + super(type); + } + + /** + * Constructs a leaf block of the given type. + * + * @param type the type of leaf block + */ + public Leaves(final Material type) { + this(type, DEFAULT_SPECIES, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given type and tree species. + * + * @param type the type of leaf block + * @param species the species of the wood block + */ + public Leaves(final Material type, TreeSpecies species) { + this(type, species, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given type and tree species and flag for + * whether this leaf block will disappear when too far from a log. + * + * @param type the type of leaf block + * @param species the species of the wood block + * @param isDecayable whether the block is permanent or can disappear + */ + public Leaves(final Material type, TreeSpecies species, boolean isDecayable) { + super(type, species); + setDecayable(isDecayable); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Leaves(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Leaves(final Material type, final byte data) { + super(type, data); + } + + /** + * Checks if this leaf block is in the process of decaying + * + * @return true if the leaf block is in the process of decaying + */ + public boolean isDecaying() { + return (getData() & 0x8) != 0; + } + + /** + * Set whether this leaf block is in the process of decaying + * + * @param isDecaying whether the block is decaying or not + */ + public void setDecaying(boolean isDecaying) { + setData((byte) ((getData() & 0x3) | (isDecaying + ? 0x8 // Clear the permanent flag to make this a decayable flag and set the decaying flag + : (getData() & 0x4)))); // Only persist the decayable flag if this is not a decaying block + } + + /** + * Checks if this leaf block is permanent or can decay when too far from a + * log + * + * @return true if the leaf block is permanent or can decay when too far + * from a log + */ + public boolean isDecayable() { + return (getData() & 0x4) == 0; + } + + /** + * Set whether this leaf block will disappear when too far from a log + * + * @param isDecayable whether the block is permanent or can disappear + */ + public void setDecayable(boolean isDecayable) { + setData((byte) ((getData() & 0x3) | (isDecayable + ? (getData() & 0x8) // Only persist the decaying flag if this is a decayable block + : 0x4))); + } + + @Override + public String toString() { + return getSpecies() + (isDecayable() ? " DECAYABLE " : " PERMANENT ") + (isDecaying() ? " DECAYING " : " ") + super.toString(); + } + + @Override + public Leaves clone() { + return (Leaves) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Lever.java b/src/main/java/org/bukkit/material/Lever.java new file mode 100644 index 00000000..c6d3882c --- /dev/null +++ b/src/main/java/org/bukkit/material/Lever.java @@ -0,0 +1,162 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents a lever + */ +public class Lever extends SimpleAttachableMaterialData implements Redstone { + public Lever() { + super(Material.LEVER); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Lever(final int type) { + super(type); + } + + public Lever(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Lever(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Lever(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set this lever to be powered or not. + * + * @param isPowered whether the lever should be powered or not + */ + public void setPowered(boolean isPowered) { + setData((byte) (isPowered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + case 0x6: + return BlockFace.DOWN; + + case 0x0: + case 0x7: + return BlockFace.UP; + + } + + return null; + } + + /** + * Sets the direction this lever is pointing in + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + BlockFace attach = getAttachedFace(); + + if (attach == BlockFace.DOWN) { + switch (face) { + case SOUTH: + case NORTH: + data |= 0x5; + break; + + case EAST: + case WEST: + data |= 0x6; + break; + } + } else if (attach == BlockFace.UP) { + switch (face) { + case SOUTH: + case NORTH: + data |= 0x7; + break; + + case EAST: + case WEST: + data |= 0x0; + break; + } + } else { + switch (face) { + case EAST: + data |= 0x1; + break; + + case WEST: + data |= 0x2; + break; + + case SOUTH: + data |= 0x3; + break; + + case NORTH: + data |= 0x4; + break; + } + } + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Lever clone() { + return (Lever) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/LongGrass.java b/src/main/java/org/bukkit/material/LongGrass.java new file mode 100644 index 00000000..5cd8d200 --- /dev/null +++ b/src/main/java/org/bukkit/material/LongGrass.java @@ -0,0 +1,79 @@ +package org.bukkit.material; + +import org.bukkit.GrassSpecies; +import org.bukkit.Material; + +/** + * Represents the different types of long grasses. + */ +public class LongGrass extends MaterialData { + public LongGrass() { + super(Material.LONG_GRASS); + } + + public LongGrass(GrassSpecies species) { + this(); + setSpecies(species); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public LongGrass(final int type) { + super(type); + } + + public LongGrass(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public LongGrass(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public LongGrass(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current species of this grass + * + * @return GrassSpecies of this grass + */ + public GrassSpecies getSpecies() { + return GrassSpecies.getByData(getData()); + } + + /** + * Sets the species of this grass + * + * @param species New species of this grass + */ + public void setSpecies(GrassSpecies species) { + setData(species.getData()); + } + + @Override + public String toString() { + return getSpecies() + " " + super.toString(); + } + + @Override + public LongGrass clone() { + return (LongGrass) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/MaterialData.java b/src/main/java/org/bukkit/material/MaterialData.java new file mode 100644 index 00000000..29826bc3 --- /dev/null +++ b/src/main/java/org/bukkit/material/MaterialData.java @@ -0,0 +1,140 @@ +package org.bukkit.material; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; + +/** + * Handles specific metadata for certain items or blocks + */ +public class MaterialData implements Cloneable { + private final int type; + private byte data = 0; + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public MaterialData(final int type) { + this(type, (byte) 0); + } + + public MaterialData(final Material type) { + this(type, (byte) 0); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MaterialData(final int type, final byte data) { + this.type = type; + this.data = data; + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MaterialData(final Material type, final byte data) { + this(type.getId(), data); + } + + /** + * Gets the raw data in this material + * + * @return Raw data + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Sets the raw data of this material + * + * @param data New raw data + * @deprecated Magic value + */ + @Deprecated + public void setData(byte data) { + this.data = data; + } + + /** + * Gets the Material that this MaterialData represents + * + * @return Material represented by this MaterialData + */ + public Material getItemType() { + return Material.getMaterial(type); + } + + /** + * Gets the Material Id that this MaterialData represents + * + * @return Material Id represented by this MaterialData + * @deprecated Magic value + */ + @Deprecated + public int getItemTypeId() { + return type; + } + + /** + * Creates a new ItemStack based on this MaterialData + * + * @return New ItemStack containing a copy of this MaterialData + * @deprecated this method creates an ItemStack of size 0 which is not + * generally useful. Consider {@link #toItemStack(int)}. + */ + @Deprecated + public ItemStack toItemStack() { + return new ItemStack(type, 0, data); + } + + /** + * Creates a new ItemStack based on this MaterialData + * + * @param amount The stack size of the new stack + * @return New ItemStack containing a copy of this MaterialData + */ + public ItemStack toItemStack(int amount) { + return new ItemStack(type, amount, data); + } + + @Override + public String toString() { + return getItemType() + "(" + getData() + ")"; + } + + @Override + public int hashCode() { + return ((getItemTypeId() << 8) ^ getData()); + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof MaterialData) { + MaterialData md = (MaterialData) obj; + + return (md.getItemTypeId() == getItemTypeId() && md.getData() == getData()); + } else { + return false; + } + } + + @Override + public MaterialData clone() { + try { + return (MaterialData) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } +} diff --git a/src/main/java/org/bukkit/material/MonsterEggs.java b/src/main/java/org/bukkit/material/MonsterEggs.java new file mode 100644 index 00000000..a6897b7e --- /dev/null +++ b/src/main/java/org/bukkit/material/MonsterEggs.java @@ -0,0 +1,69 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of monster eggs + */ +public class MonsterEggs extends TexturedMaterial { + + private static final List textures = new ArrayList(); + static { + textures.add(Material.STONE); + textures.add(Material.COBBLESTONE); + textures.add(Material.SMOOTH_BRICK); + } + + public MonsterEggs() { + super(Material.MONSTER_EGGS); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public MonsterEggs(final int type) { + super(type); + } + + public MonsterEggs(final Material type) { + super((textures.contains(type)) ? Material.MONSTER_EGGS : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MonsterEggs(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MonsterEggs(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + @Override + public MonsterEggs clone() { + return (MonsterEggs) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Mushroom.java b/src/main/java/org/bukkit/material/Mushroom.java new file mode 100644 index 00000000..ff5ae088 --- /dev/null +++ b/src/main/java/org/bukkit/material/Mushroom.java @@ -0,0 +1,294 @@ +package org.bukkit.material; + +import java.util.EnumSet; +import java.util.Set; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.material.types.MushroomBlockTexture; + +/** + * Represents a huge mushroom block with certain combinations of faces set to + * cap, pores or stem. + * + * @see Material#HUGE_MUSHROOM_1 + * @see Material#HUGE_MUSHROOM_2 + */ +public class Mushroom extends MaterialData { + private static final byte NORTH_LIMIT = 4; + private static final byte SOUTH_LIMIT = 6; + private static final byte EAST_WEST_LIMIT = 3; + private static final byte EAST_REMAINDER = 0; + private static final byte WEST_REMAINDER = 1; + private static final byte NORTH_SOUTH_MOD = 3; + private static final byte EAST_WEST_MOD = 1; + + /** + * Constructs a brown/red mushroom block with all sides set to pores. + * + * @param shroom A brown or red mushroom material type. + * + * @see Material#HUGE_MUSHROOM_1 + * @see Material#HUGE_MUSHROOM_2 + */ + public Mushroom(Material shroom) { + super(shroom); + Validate.isTrue(shroom == Material.HUGE_MUSHROOM_1 || shroom == Material.HUGE_MUSHROOM_2, "Not a mushroom!"); + } + + /** + * Constructs a brown/red mushroom cap block with the specified face or + * faces set to cap texture. + * + * Setting any of the four sides will also set the top to cap. + * + * To set two side faces at once use e.g. north-west. + * + * Specify self to set all six faces at once. + * + * @param shroom A brown or red mushroom material type. + * @param capFace The face or faces to set to mushroom cap texture. + * + * @see Material#HUGE_MUSHROOM_1 + * @see Material#HUGE_MUSHROOM_2 + * @see BlockFace + */ + public Mushroom(Material shroom, BlockFace capFace) { + this(shroom, MushroomBlockTexture.getCapByFace(capFace)); + } + + /** + * Constructs a brown/red mushroom block with the specified textures. + * + * @param shroom A brown or red mushroom material type. + * @param texture The textured mushroom faces. + * + * @see Material#HUGE_MUSHROOM_1 + * @see Material#HUGE_MUSHROOM_2 + */ + public Mushroom(Material shroom, MushroomBlockTexture texture) { + this(shroom, texture.getData()); + } + + /** + * @param shroom the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Mushroom(Material shroom, byte data) { + super(shroom, data); + Validate.isTrue(shroom == Material.HUGE_MUSHROOM_1 || shroom == Material.HUGE_MUSHROOM_2, "Not a mushroom!"); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Mushroom(int type, byte data) { + super(type, data); + Validate.isTrue(type == Material.HUGE_MUSHROOM_1.getId() || type == Material.HUGE_MUSHROOM_2.getId(), "Not a mushroom!"); + } + + /** + * @return Whether this is a mushroom stem. + */ + public boolean isStem() { + return getData() == MushroomBlockTexture.STEM_SIDES.getData() || getData() == MushroomBlockTexture.ALL_STEM.getData(); + } + + /** + * Sets this to be a mushroom stem. + * + * @see MushroomBlockTexture#STEM_SIDES + * @see MushroomBlockTexture#ALL_STEM + * + * @deprecated Use + * {@link #setBlockTexture(MushroomBlockTexture)} + * with {@link MushroomBlockTexture#STEM_SIDES } or + * {@link MushroomBlockTexture#ALL_STEM} + */ + @Deprecated + public void setStem() { + setData((byte) MushroomBlockTexture.STEM_SIDES.getData()); + } + + /** + * Gets the mushroom texture of this block. + * + * @return The mushroom texture of this block + */ + public MushroomBlockTexture getBlockTexture() { + return MushroomBlockTexture.getByData(getData()); + } + + /** + * Sets the mushroom texture of this block. + * + * @param texture The mushroom texture to set + */ + public void setBlockTexture(MushroomBlockTexture texture) { + setData(texture.getData()); + } + + /** + * Checks whether a face of the block is painted with cap texture. + * + * @param face The face to check. + * @return True if it is painted. + */ + public boolean isFacePainted(BlockFace face) { + byte data = getData(); + + if (data == MushroomBlockTexture.ALL_PORES.getData() || data == MushroomBlockTexture.STEM_SIDES.getData() + || data == MushroomBlockTexture.ALL_STEM.getData()) { + return false; + } + + switch (face) { + case WEST: + return data < NORTH_LIMIT; + case EAST: + return data > SOUTH_LIMIT; + case NORTH: + return data % EAST_WEST_LIMIT == EAST_REMAINDER; + case SOUTH: + return data % EAST_WEST_LIMIT == WEST_REMAINDER; + case UP: + return true; + case DOWN: + case SELF: + return data == MushroomBlockTexture.ALL_CAP.getData(); + default: + return false; + } + } + + /** + * Set a face of the block to be painted or not. Note that due to the + * nature of how the data is stored, setting a face painted or not is not + * guaranteed to leave the other faces unchanged. + * + * @param face The face to paint or unpaint. + * @param painted True if you want to paint it, false if you want the + * pores to show. + * + * @deprecated Use MushroomBlockType cap options + */ + @Deprecated + public void setFacePainted(BlockFace face, boolean painted) { + if (painted == isFacePainted(face)) { + return; + } + + byte data = getData(); + + if (data == MushroomBlockTexture.ALL_PORES.getData() || isStem()) { + data = MushroomBlockTexture.CAP_TOP.getData(); + } + if (data == MushroomBlockTexture.ALL_CAP.getData() && !painted) { + data = MushroomBlockTexture.CAP_TOP.getData(); + face = face.getOppositeFace(); + painted = true; + } + + switch (face) { + case WEST: + if (painted) { + data -= NORTH_SOUTH_MOD; + } else { + data += NORTH_SOUTH_MOD; + } + + break; + case EAST: + if (painted) { + data += NORTH_SOUTH_MOD; + } else { + data -= NORTH_SOUTH_MOD; + } + + break; + case NORTH: + if (painted) { + data += EAST_WEST_MOD; + } else { + data -= EAST_WEST_MOD; + } + + break; + case SOUTH: + if (painted) { + data -= EAST_WEST_MOD; + } else { + data += EAST_WEST_MOD; + } + + break; + case UP: + if (!painted) { + data = MushroomBlockTexture.ALL_PORES.getData(); + } + break; + case SELF: + case DOWN: + if (painted) { + data = MushroomBlockTexture.ALL_CAP.getData(); + } else { + data = MushroomBlockTexture.ALL_PORES.getData(); + } + break; + default: + throw new IllegalArgumentException("Can't paint that face of a mushroom!"); + } + + setData(data); + } + + /** + * @return A set of all faces that are currently painted (an empty set if + * it is a stem) + */ + public Set getPaintedFaces() { + EnumSet faces = EnumSet.noneOf(BlockFace.class); + + if (isFacePainted(BlockFace.WEST)) { + faces.add(BlockFace.WEST); + } + + if (isFacePainted(BlockFace.NORTH)) { + faces.add(BlockFace.NORTH); + } + + if (isFacePainted(BlockFace.SOUTH)) { + faces.add(BlockFace.SOUTH); + } + + if (isFacePainted(BlockFace.EAST)) { + faces.add(BlockFace.EAST); + } + + if (isFacePainted(BlockFace.UP)) { + faces.add(BlockFace.UP); + } + + if (isFacePainted(BlockFace.DOWN)) { + faces.add(BlockFace.DOWN); + } + + return faces; + } + + @Override + public String toString() { + return Material.getMaterial(getItemTypeId()).toString() + (isStem() ? " STEM " : " CAP ") + getPaintedFaces(); + } + + @Override + public Mushroom clone() { + return (Mushroom) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/NetherWarts.java b/src/main/java/org/bukkit/material/NetherWarts.java new file mode 100644 index 00000000..663b1a10 --- /dev/null +++ b/src/main/java/org/bukkit/material/NetherWarts.java @@ -0,0 +1,101 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.NetherWartsState; + +/** + * Represents nether wart + */ +public class NetherWarts extends MaterialData { + public NetherWarts() { + super(Material.NETHER_WARTS); + } + + public NetherWarts(NetherWartsState state) { + this(); + setState(state); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public NetherWarts(final int type) { + super(type); + } + + public NetherWarts(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public NetherWarts(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public NetherWarts(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current growth state of this nether wart + * + * @return NetherWartsState of this nether wart + */ + public NetherWartsState getState() { + switch (getData()) { + case 0: + return NetherWartsState.SEEDED; + case 1: + return NetherWartsState.STAGE_ONE; + case 2: + return NetherWartsState.STAGE_TWO; + default: + return NetherWartsState.RIPE; + } + } + + /** + * Sets the growth state of this nether wart + * + * @param state New growth state of this nether wart + */ + public void setState(NetherWartsState state) { + switch (state) { + case SEEDED: + setData((byte) 0x0); + return; + case STAGE_ONE: + setData((byte) 0x1); + return; + case STAGE_TWO: + setData((byte) 0x2); + return; + case RIPE: + setData((byte) 0x3); + return; + } + } + + @Override + public String toString() { + return getState() + " " + super.toString(); + } + + @Override + public NetherWarts clone() { + return (NetherWarts) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Observer.java b/src/main/java/org/bukkit/material/Observer.java new file mode 100644 index 00000000..fc3a7b17 --- /dev/null +++ b/src/main/java/org/bukkit/material/Observer.java @@ -0,0 +1,117 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents an observer. + */ +public class Observer extends MaterialData implements Directional, Redstone { + + public Observer() { + super(Material.OBSERVER); + } + + public Observer(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Observer(final int type) { + super(type); + } + + public Observer(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Observer(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Observer(final Material type, final byte data) { + super(type, data); + } + + @Override + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + @Override + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case DOWN: + data |= 0x0; + break; + case UP: + data |= 0x1; + break; + case SOUTH: + data |= 0x2; + break; + case NORTH: + data |= 0x3; + break; + case EAST: + data |= 0x4; + break; + case WEST: + data |= 0x5; + break; + } + + setData(data); + } + + @Override + public BlockFace getFacing() { + int data = getData() & 0x7; + + switch (data) { + case 0x0: + return BlockFace.DOWN; + case 0x1: + return BlockFace.UP; + case 0x2: + return BlockFace.SOUTH; + case 0x3: + return BlockFace.NORTH; + case 0x4: + return BlockFace.EAST; + case 0x5: + return BlockFace.WEST; + default: + throw new IllegalArgumentException("Illegal facing direction " + data); + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Observer clone() { + return (Observer) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Openable.java b/src/main/java/org/bukkit/material/Openable.java new file mode 100644 index 00000000..0ae54f97 --- /dev/null +++ b/src/main/java/org/bukkit/material/Openable.java @@ -0,0 +1,18 @@ +package org.bukkit.material; + +public interface Openable { + + /** + * Check to see if the door is open. + * + * @return true if the door has swung counterclockwise around its hinge. + */ + boolean isOpen(); + + /** + * Configure this door to be either open or closed; + * + * @param isOpen True to open the door. + */ + void setOpen(boolean isOpen); +} diff --git a/src/main/java/org/bukkit/material/PistonBaseMaterial.java b/src/main/java/org/bukkit/material/PistonBaseMaterial.java new file mode 100644 index 00000000..bbf1565d --- /dev/null +++ b/src/main/java/org/bukkit/material/PistonBaseMaterial.java @@ -0,0 +1,123 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Material data for the piston base block + */ +public class PistonBaseMaterial extends MaterialData implements Directional, Redstone { + + /** + * Constructs a PistonBaseMaterial + * + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public PistonBaseMaterial(final int type) { + super(type); + } + + public PistonBaseMaterial(final Material type) { + super(type); + } + + /** + * Constructs a PistonBaseMaterial. + * + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonBaseMaterial(final int type, final byte data) { + super(type, data); + } + + /** + * Constructs a PistonBaseMaterial. + * + * @param type the material type to use + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonBaseMaterial(final Material type, final byte data) { + super(type, data); + } + + @Override + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case UP: + data |= 1; + break; + case NORTH: + data |= 2; + break; + case SOUTH: + data |= 3; + break; + case WEST: + data |= 4; + break; + case EAST: + data |= 5; + break; + } + setData(data); + } + + @Override + public BlockFace getFacing() { + byte dir = (byte) (getData() & 7); + + switch (dir) { + case 0: + return BlockFace.DOWN; + case 1: + return BlockFace.UP; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.SOUTH; + case 4: + return BlockFace.WEST; + case 5: + return BlockFace.EAST; + default: + return BlockFace.SELF; + } + } + + @Override + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Sets the current state of this piston + * + * @param powered true if the piston is extended {@literal &} powered, or false + */ + public void setPowered(boolean powered) { + setData((byte) (powered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Checks if this piston base is sticky, and returns true if so + * + * @return true if this piston is "sticky", or false + */ + public boolean isSticky() { + return this.getItemType() == Material.PISTON_STICKY_BASE; + } + + @Override + public PistonBaseMaterial clone() { + return (PistonBaseMaterial) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/PistonExtensionMaterial.java b/src/main/java/org/bukkit/material/PistonExtensionMaterial.java new file mode 100644 index 00000000..8076ec97 --- /dev/null +++ b/src/main/java/org/bukkit/material/PistonExtensionMaterial.java @@ -0,0 +1,113 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Material data for the piston extension block + */ +public class PistonExtensionMaterial extends MaterialData implements Attachable { + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public PistonExtensionMaterial(final int type) { + super(type); + } + + public PistonExtensionMaterial(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonExtensionMaterial(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonExtensionMaterial(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case UP: + data |= 1; + break; + case NORTH: + data |= 2; + break; + case SOUTH: + data |= 3; + break; + case WEST: + data |= 4; + break; + case EAST: + data |= 5; + break; + } + setData(data); + } + + public BlockFace getFacing() { + byte dir = (byte) (getData() & 7); + + switch (dir) { + case 0: + return BlockFace.DOWN; + case 1: + return BlockFace.UP; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.SOUTH; + case 4: + return BlockFace.WEST; + case 5: + return BlockFace.EAST; + default: + return BlockFace.SELF; + } + } + + /** + * Checks if this piston extension is sticky, and returns true if so + * + * @return true if this piston is "sticky", or false + */ + public boolean isSticky() { + return (getData() & 8) == 8; + } + + /** + * Sets whether or not this extension is sticky + * + * @param sticky true if sticky, otherwise false + */ + public void setSticky(boolean sticky) { + setData((byte) (sticky ? (getData() | 0x8) : (getData() & ~0x8))); + } + + public BlockFace getAttachedFace() { + return getFacing().getOppositeFace(); + } + + @Override + public PistonExtensionMaterial clone() { + return (PistonExtensionMaterial) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/PoweredRail.java b/src/main/java/org/bukkit/material/PoweredRail.java new file mode 100644 index 00000000..4a9d6a97 --- /dev/null +++ b/src/main/java/org/bukkit/material/PoweredRail.java @@ -0,0 +1,63 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a powered rail + */ +public class PoweredRail extends ExtendedRails implements Redstone { + public PoweredRail() { + super(Material.POWERED_RAIL); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public PoweredRail(final int type) { + super(type); + } + + public PoweredRail(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PoweredRail(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PoweredRail(final Material type, final byte data) { + super(type, data); + } + + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set whether this PoweredRail should be powered or not. + * + * @param isPowered whether or not the rail is powered + */ + public void setPowered(boolean isPowered) { + setData((byte) (isPowered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public PoweredRail clone() { + return (PoweredRail) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/PressurePlate.java b/src/main/java/org/bukkit/material/PressurePlate.java new file mode 100644 index 00000000..d7747b5c --- /dev/null +++ b/src/main/java/org/bukkit/material/PressurePlate.java @@ -0,0 +1,59 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a pressure plate + */ +public class PressurePlate extends MaterialData implements PressureSensor { + public PressurePlate() { + super(Material.WOOD_PLATE); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public PressurePlate(int type) { + super(type); + } + + public PressurePlate(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PressurePlate(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PressurePlate(Material type, byte data) { + super(type, data); + } + + public boolean isPressed() { + return getData() == 0x1; + } + + @Override + public String toString() { + return super.toString() + (isPressed() ? " PRESSED" : ""); + } + + @Override + public PressurePlate clone() { + return (PressurePlate) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/PressureSensor.java b/src/main/java/org/bukkit/material/PressureSensor.java new file mode 100644 index 00000000..de20bd39 --- /dev/null +++ b/src/main/java/org/bukkit/material/PressureSensor.java @@ -0,0 +1,5 @@ +package org.bukkit.material; + +public interface PressureSensor { + public boolean isPressed(); +} diff --git a/src/main/java/org/bukkit/material/Pumpkin.java b/src/main/java/org/bukkit/material/Pumpkin.java new file mode 100644 index 00000000..afd200a1 --- /dev/null +++ b/src/main/java/org/bukkit/material/Pumpkin.java @@ -0,0 +1,114 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a pumpkin. + */ +public class Pumpkin extends MaterialData implements Directional { + + public Pumpkin() { + super(Material.PUMPKIN); + } + + /** + * Instantiate a pumpkin facing in a particular direction. + * + * @param direction the direction the pumkin's face is facing + */ + public Pumpkin(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Pumpkin(final int type) { + super(type); + } + + public Pumpkin(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Pumpkin(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Pumpkin(final Material type, final byte data) { + super(type, data); + } + + public boolean isLit() { + return getItemType() == Material.JACK_O_LANTERN; + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x0; + break; + + case EAST: + data = 0x1; + break; + + case SOUTH: + data = 0x2; + break; + + case WEST: + default: + data = 0x3; + } + + setData(data); + } + + public BlockFace getFacing() { + byte data = getData(); + + switch (data) { + case 0x0: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + (isLit() ? "" : "NOT ") + "LIT"; + } + + @Override + public Pumpkin clone() { + return (Pumpkin) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Rails.java b/src/main/java/org/bukkit/material/Rails.java new file mode 100644 index 00000000..10044ee8 --- /dev/null +++ b/src/main/java/org/bukkit/material/Rails.java @@ -0,0 +1,178 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents minecart rails. + */ +public class Rails extends MaterialData { + + public Rails() { + super(Material.RAILS); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Rails(final int type) { + super(type); + } + + public Rails(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Rails(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Rails(final Material type, final byte data) { + super(type, data); + } + + /** + * @return the whether this track is set on a slope + */ + public boolean isOnSlope() { + byte d = getConvertedData(); + + return (d == 0x2 || d == 0x3 || d == 0x4 || d == 0x5); + } + + /** + * @return the whether this track is set as a curve + */ + public boolean isCurve() { + byte d = getConvertedData(); + + return (d == 0x6 || d == 0x7 || d == 0x8 || d == 0x9); + } + + /** + * @return the direction these tracks are set + *

    + * Note that tracks are bidirectional and that the direction returned + * is the ascending direction if the track is set on a slope. If it is + * set as a curve, the corner of the track is returned. + */ + public BlockFace getDirection() { + byte d = getConvertedData(); + + switch (d) { + case 0x0: + default: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.WEST; + + case 0x4: + return BlockFace.NORTH; + + case 0x5: + return BlockFace.SOUTH; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_EAST; + + case 0x8: + return BlockFace.SOUTH_EAST; + + case 0x9: + return BlockFace.SOUTH_WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getDirection() + (isCurve() ? " on a curve" : (isOnSlope() ? " on a slope" : "")); + } + + /** + * Return the data without the extended properties used by {@link + * PoweredRail} and {@link DetectorRail}. Overridden in {@link + * ExtendedRails} + * + * @return the data without the extended part + * @deprecated Magic value + */ + @Deprecated + protected byte getConvertedData() { + return getData(); + } + + /** + * Set the direction of these tracks + *

    + * Note that tracks are bidirectional and that the direction returned is + * the ascending direction if the track is set on a slope. If it is set as + * a curve, the corner of the track should be supplied. + * + * @param face the direction the track should be facing + * @param isOnSlope whether or not the track should be on a slope + */ + public void setDirection(BlockFace face, boolean isOnSlope) { + switch (face) { + case EAST: + setData((byte) (isOnSlope ? 0x2 : 0x1)); + break; + + case WEST: + setData((byte) (isOnSlope ? 0x3 : 0x1)); + break; + + case NORTH: + setData((byte) (isOnSlope ? 0x4 : 0x0)); + break; + + case SOUTH: + setData((byte) (isOnSlope ? 0x5 : 0x0)); + break; + + case NORTH_WEST: + setData((byte) 0x6); + break; + + case NORTH_EAST: + setData((byte) 0x7); + break; + + case SOUTH_EAST: + setData((byte) 0x8); + break; + + case SOUTH_WEST: + setData((byte) 0x9); + break; + } + } + + @Override + public Rails clone() { + return (Rails) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Redstone.java b/src/main/java/org/bukkit/material/Redstone.java new file mode 100644 index 00000000..3e46603f --- /dev/null +++ b/src/main/java/org/bukkit/material/Redstone.java @@ -0,0 +1,15 @@ +package org.bukkit.material; + +/** + * Indicated a Material that may carry or create a Redstone current + */ +public interface Redstone { + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered(); +} diff --git a/src/main/java/org/bukkit/material/RedstoneTorch.java b/src/main/java/org/bukkit/material/RedstoneTorch.java new file mode 100644 index 00000000..45c3e47f --- /dev/null +++ b/src/main/java/org/bukkit/material/RedstoneTorch.java @@ -0,0 +1,65 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a redstone torch + */ +public class RedstoneTorch extends Torch implements Redstone { + public RedstoneTorch() { + super(Material.REDSTONE_TORCH_ON); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public RedstoneTorch(final int type) { + super(type); + } + + public RedstoneTorch(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneTorch(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneTorch(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return getItemType() == Material.REDSTONE_TORCH_ON; + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public RedstoneTorch clone() { + return (RedstoneTorch) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/RedstoneWire.java b/src/main/java/org/bukkit/material/RedstoneWire.java new file mode 100644 index 00000000..d13ae4bf --- /dev/null +++ b/src/main/java/org/bukkit/material/RedstoneWire.java @@ -0,0 +1,65 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents redstone wire + */ +public class RedstoneWire extends MaterialData implements Redstone { + public RedstoneWire() { + super(Material.REDSTONE_WIRE); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public RedstoneWire(final int type) { + super(type); + } + + public RedstoneWire(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneWire(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneWire(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return getData() > 0; + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public RedstoneWire clone() { + return (RedstoneWire) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Sandstone.java b/src/main/java/org/bukkit/material/Sandstone.java new file mode 100644 index 00000000..be88d43b --- /dev/null +++ b/src/main/java/org/bukkit/material/Sandstone.java @@ -0,0 +1,79 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.SandstoneType; + +/** + * Represents the different types of sandstone. + */ +public class Sandstone extends MaterialData { + public Sandstone() { + super(Material.SANDSTONE); + } + + public Sandstone(SandstoneType type) { + this(); + setType(type); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Sandstone(final int type) { + super(type); + } + + public Sandstone(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sandstone(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sandstone(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current type of this sandstone + * + * @return SandstoneType of this sandstone + */ + public SandstoneType getType() { + return SandstoneType.getByData(getData()); + } + + /** + * Sets the type of this sandstone + * + * @param type New type of this sandstone + */ + public void setType(SandstoneType type) { + setData(type.getData()); + } + + @Override + public String toString() { + return getType() + " " + super.toString(); + } + + @Override + public Sandstone clone() { + return (Sandstone) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Sapling.java b/src/main/java/org/bukkit/material/Sapling.java new file mode 100644 index 00000000..08ef2de7 --- /dev/null +++ b/src/main/java/org/bukkit/material/Sapling.java @@ -0,0 +1,121 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of Tree block that face a direction. + * + * @see Material#SAPLING + */ +public class Sapling extends Wood { + + /** + * Constructs a sapling. + */ + public Sapling() { + this(DEFAULT_SPECIES); + } + + /** + * Constructs a sapling of the given tree species. + * + * @param species the species of the sapling + */ + public Sapling(TreeSpecies species) { + this(species, false); + } + + /** + * Constructs a sapling of the given tree species and if is it instant + * growable + * + * @param species the species of the tree block + * @param isInstantGrowable true if the Sapling should grow when next ticked with bonemeal + */ + public Sapling(TreeSpecies species, boolean isInstantGrowable) { + this(Material.SAPLING, species, isInstantGrowable); + } + + /** + * Constructs a sapling of the given type. + * + * @param type the type of tree block + */ + public Sapling(final Material type) { + this(type, DEFAULT_SPECIES, false); + } + + /** + * Constructs a sapling of the given type and tree species. + * + * @param type the type of sapling + * @param species the species of the sapling + */ + public Sapling(final Material type, TreeSpecies species) { + this(type, species, false); + } + + /** + * Constructs a sapling of the given type and tree species and if is it + * instant growable + * + * @param type the type of sapling + * @param species the species of the sapling + * @param isInstantGrowable true if the Sapling should grow when next ticked + * with bonemeal + */ + public Sapling(final Material type, TreeSpecies species, boolean isInstantGrowable) { + super(type, species); + setIsInstantGrowable(isInstantGrowable); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sapling(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sapling(final Material type, final byte data) { + super(type, data); + } + + /** + * Checks if the Sapling would grow when next ticked with bonemeal + * + * @return true if the Sapling would grow when next ticked with bonemeal + */ + public boolean isInstantGrowable() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set whether this sapling will grow when next ticked with bonemeal + * + * @param isInstantGrowable true if the Sapling should grow when next ticked + * with bonemeal + */ + public void setIsInstantGrowable(boolean isInstantGrowable) { + setData(isInstantGrowable ? (byte) ((getData() & 0x7) | 0x8) : (byte) (getData() & 0x7)); + } + + @Override + public String toString() { + return getSpecies() + " " + (isInstantGrowable() ? " IS_INSTANT_GROWABLE " : "") + " " + super.toString(); + } + + @Override + public Sapling clone() { + return (Sapling) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Sign.java b/src/main/java/org/bukkit/material/Sign.java new file mode 100644 index 00000000..0accdbce --- /dev/null +++ b/src/main/java/org/bukkit/material/Sign.java @@ -0,0 +1,252 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * MaterialData for signs + */ +public class Sign extends MaterialData implements Attachable { + public Sign() { + super(Material.SIGN_POST); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Sign(final int type) { + super(type); + } + + public Sign(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sign(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sign(final Material type, final byte data) { + super(type, data); + } + + /** + * Check if this sign is attached to a wall + * + * @return true if this sign is attached to a wall, false if set on top of + * a block + */ + public boolean isWallSign() { + return getItemType() == Material.WALL_SIGN; + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + if (isWallSign()) { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } else { + return BlockFace.DOWN; + } + } + + /** + * Gets the direction that this sign is currently facing + * + * @return BlockFace indicating where this sign is facing + */ + public BlockFace getFacing() { + byte data = getData(); + + if (!isWallSign()) { + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.SOUTH_SOUTH_WEST; + + case 0x2: + return BlockFace.SOUTH_WEST; + + case 0x3: + return BlockFace.WEST_SOUTH_WEST; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.WEST_NORTH_WEST; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_NORTH_WEST; + + case 0x8: + return BlockFace.NORTH; + + case 0x9: + return BlockFace.NORTH_NORTH_EAST; + + case 0xA: + return BlockFace.NORTH_EAST; + + case 0xB: + return BlockFace.EAST_NORTH_EAST; + + case 0xC: + return BlockFace.EAST; + + case 0xD: + return BlockFace.EAST_SOUTH_EAST; + + case 0xE: + return BlockFace.SOUTH_EAST; + + case 0xF: + return BlockFace.SOUTH_SOUTH_EAST; + } + + return null; + } else { + return getAttachedFace().getOppositeFace(); + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + if (isWallSign()) { + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + } else { + switch (face) { + case SOUTH: + data = 0x0; + break; + + case SOUTH_SOUTH_WEST: + data = 0x1; + break; + + case SOUTH_WEST: + data = 0x2; + break; + + case WEST_SOUTH_WEST: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case WEST_NORTH_WEST: + data = 0x5; + break; + + case NORTH_WEST: + data = 0x6; + break; + + case NORTH_NORTH_WEST: + data = 0x7; + break; + + case NORTH: + data = 0x8; + break; + + case NORTH_NORTH_EAST: + data = 0x9; + break; + + case NORTH_EAST: + data = 0xA; + break; + + case EAST_NORTH_EAST: + data = 0xB; + break; + + case EAST: + data = 0xC; + break; + + case EAST_SOUTH_EAST: + data = 0xD; + break; + + case SOUTH_SOUTH_EAST: + data = 0xF; + break; + + case SOUTH_EAST: + default: + data = 0xE; + } + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Sign clone() { + return (Sign) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java b/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java new file mode 100644 index 00000000..b1897b7b --- /dev/null +++ b/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java @@ -0,0 +1,68 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Simple utility class for attachable MaterialData subclasses + */ +public abstract class SimpleAttachableMaterialData extends MaterialData implements Attachable { + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public SimpleAttachableMaterialData(int type) { + super(type); + } + + public SimpleAttachableMaterialData(int type, BlockFace direction) { + this(type); + setFacingDirection(direction); + } + + public SimpleAttachableMaterialData(Material type, BlockFace direction) { + this(type); + setFacingDirection(direction); + } + + public SimpleAttachableMaterialData(Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SimpleAttachableMaterialData(int type, byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SimpleAttachableMaterialData(Material type, byte data) { + super(type, data); + } + + public BlockFace getFacing() { + BlockFace attachedFace = getAttachedFace(); + return attachedFace == null ? null : attachedFace.getOppositeFace(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public SimpleAttachableMaterialData clone() { + return (SimpleAttachableMaterialData) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Skull.java b/src/main/java/org/bukkit/material/Skull.java new file mode 100644 index 00000000..498deb65 --- /dev/null +++ b/src/main/java/org/bukkit/material/Skull.java @@ -0,0 +1,116 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a skull. + */ +public class Skull extends MaterialData implements Directional { + public Skull() { + super(Material.SKULL); + } + + /** + * Instantiate a skull facing in a particular direction. + * + * @param direction the direction the skull's face is facing + */ + public Skull(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Skull(final int type) { + super(type); + } + + public Skull(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Skull(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Skull(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + int data; + + switch (face) { + case SELF: + default: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case WEST: + data = 0x4; + break; + + case SOUTH: + data = 0x3; + break; + + case EAST: + data = 0x5; + } + + setData((byte) data); + } + + public BlockFace getFacing() { + int data = getData(); + + switch (data) { + case 0x1: + default: + return BlockFace.SELF; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Skull clone() { + return (Skull) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/SmoothBrick.java b/src/main/java/org/bukkit/material/SmoothBrick.java new file mode 100644 index 00000000..a6d8931e --- /dev/null +++ b/src/main/java/org/bukkit/material/SmoothBrick.java @@ -0,0 +1,70 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of smooth bricks. + */ +public class SmoothBrick extends TexturedMaterial { + + private static final List textures = new ArrayList(); + static { + textures.add(Material.STONE); + textures.add(Material.MOSSY_COBBLESTONE); + textures.add(Material.COBBLESTONE); + textures.add(Material.SMOOTH_BRICK); + } + + public SmoothBrick() { + super(Material.SMOOTH_BRICK); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public SmoothBrick(final int type) { + super(type); + } + + public SmoothBrick(final Material type) { + super((textures.contains(type)) ? Material.SMOOTH_BRICK : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SmoothBrick(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SmoothBrick(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + @Override + public SmoothBrick clone() { + return (SmoothBrick) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/SpawnEgg.java b/src/main/java/org/bukkit/material/SpawnEgg.java new file mode 100644 index 00000000..a8613a0c --- /dev/null +++ b/src/main/java/org/bukkit/material/SpawnEgg.java @@ -0,0 +1,73 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.meta.SpawnEggMeta; + +/** + * Represents a spawn egg that can be used to spawn mobs + * @deprecated use {@link SpawnEggMeta} + */ +@Deprecated +public class SpawnEgg extends MaterialData { + + public SpawnEgg() { + super(Material.MONSTER_EGG); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SpawnEgg(int type, byte data) { + super(type, data); + } + + /** + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SpawnEgg(byte data) { + super(Material.MONSTER_EGG, data); + } + + public SpawnEgg(EntityType type) { + this(); + setSpawnedType(type); + } + + /** + * Get the type of entity this egg will spawn. + * + * @return The entity type. + * @deprecated This is now stored in {@link SpawnEggMeta}. + */ + @Deprecated + public EntityType getSpawnedType() { + return EntityType.fromId(getData()); + } + + /** + * Set the type of entity this egg will spawn. + * + * @param type The entity type. + * @deprecated This is now stored in {@link SpawnEggMeta}. + */ + @Deprecated + public void setSpawnedType(EntityType type) { + setData((byte) type.getTypeId()); + } + + @Override + public String toString() { + return "SPAWN EGG{" + getSpawnedType() + "}"; + } + + @Override + public SpawnEgg clone() { + return (SpawnEgg) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Stairs.java b/src/main/java/org/bukkit/material/Stairs.java new file mode 100644 index 00000000..80c6fb1b --- /dev/null +++ b/src/main/java/org/bukkit/material/Stairs.java @@ -0,0 +1,140 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents stairs. + */ +public class Stairs extends MaterialData implements Directional { + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Stairs(final int type) { + super(type); + } + + public Stairs(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Stairs(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Stairs(final Material type, final byte data) { + super(type, data); + } + + /** + * @return the direction the stairs ascend towards + */ + public BlockFace getAscendingDirection() { + byte data = getData(); + + switch (data & 0x3) { + case 0x0: + default: + return BlockFace.EAST; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + } + } + + /** + * @return the direction the stairs descend towards + */ + public BlockFace getDescendingDirection() { + return getAscendingDirection().getOppositeFace(); + } + + /** + * Set the direction the stair part of the block is facing + */ + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x3; + break; + + case SOUTH: + data = 0x2; + break; + + case EAST: + default: + data = 0x0; + break; + + case WEST: + data = 0x1; + break; + } + + setData((byte) ((getData() & 0xC) | data)); + } + + /** + * @return the direction the stair part of the block is facing + */ + public BlockFace getFacing() { + return getDescendingDirection(); + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x4) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is + * normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x3; + if (inv) { + dat |= 0x4; + } + setData((byte) dat); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + (isInverted() ? " inverted" : ""); + } + + @Override + public Stairs clone() { + return (Stairs) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Step.java b/src/main/java/org/bukkit/material/Step.java new file mode 100644 index 00000000..7f46af26 --- /dev/null +++ b/src/main/java/org/bukkit/material/Step.java @@ -0,0 +1,121 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of steps. + */ +public class Step extends TexturedMaterial { + private static final List textures = new ArrayList(); + static { + textures.add(Material.STONE); + textures.add(Material.SANDSTONE); + textures.add(Material.WOOD); + textures.add(Material.COBBLESTONE); + textures.add(Material.BRICK); + textures.add(Material.SMOOTH_BRICK); + textures.add(Material.NETHER_BRICK); + textures.add(Material.QUARTZ_BLOCK); + } + + public Step() { + super(Material.STEP); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Step(final int type) { + super(type); + } + + public Step(final Material type) { + super((textures.contains(type)) ? Material.STEP : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Step(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Step(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is + * normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected int getTextureIndex() { + return getData() & 0x7; + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected void setTextureIndex(int idx) { + setData((byte) ((getData() & 0x8) | idx)); + } + + @Override + public Step clone() { + return (Step) super.clone(); + } + + @Override + public String toString() { + return super.toString() + (isInverted() ? "inverted" : ""); + } +} diff --git a/src/main/java/org/bukkit/material/TexturedMaterial.java b/src/main/java/org/bukkit/material/TexturedMaterial.java new file mode 100644 index 00000000..93f56643 --- /dev/null +++ b/src/main/java/org/bukkit/material/TexturedMaterial.java @@ -0,0 +1,112 @@ +package org.bukkit.material; + +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents textured materials like steps and smooth bricks + */ +public abstract class TexturedMaterial extends MaterialData { + + public TexturedMaterial(Material m) { + super(m); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public TexturedMaterial(int type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TexturedMaterial(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TexturedMaterial(final Material type, final byte data) { + super(type, data); + } + + /** + * Retrieve a list of possible textures. The first element of the list + * will be used as a default. + * + * @return a list of possible textures for this block + */ + public abstract List getTextures(); + + /** + * Gets the current Material this block is made of + * + * @return Material of this block + */ + public Material getMaterial() { + int n = getTextureIndex(); + if (n > getTextures().size() - 1) { + n = 0; + } + + return getTextures().get(n); + } + + /** + * Sets the material this block is made of + * + * @param material + * New material of this block + */ + public void setMaterial(Material material) { + if (getTextures().contains(material)) { + setTextureIndex(getTextures().indexOf(material)); + } else { + setTextureIndex(0x0); + } + } + + /** + * Get material index from data + * + * @return index of data in textures list + * @deprecated Magic value + */ + @Deprecated + protected int getTextureIndex() { + return getData(); // Default to using all bits - override for other mappings + } + + /** + * Set material index + * + * @param idx - index of data in textures list + * @deprecated Magic value + */ + @Deprecated + protected void setTextureIndex(int idx) { + setData((byte) idx); // Default to using all bits - override for other mappings + } + + @Override + public String toString() { + return getMaterial() + " " + super.toString(); + } + + @Override + public TexturedMaterial clone() { + return (TexturedMaterial) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Torch.java b/src/main/java/org/bukkit/material/Torch.java new file mode 100644 index 00000000..60be6bd1 --- /dev/null +++ b/src/main/java/org/bukkit/material/Torch.java @@ -0,0 +1,106 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * MaterialData for torches + */ +public class Torch extends SimpleAttachableMaterialData { + public Torch() { + super(Material.TORCH); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Torch(final int type) { + super(type); + } + + public Torch(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Torch(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Torch(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = getData(); + + switch (data) { + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + default: + return BlockFace.DOWN; + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case EAST: + data = 0x1; + break; + + case WEST: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case NORTH: + data = 0x4; + break; + + case UP: + default: + data = 0x5; + } + + setData(data); + } + + @Override + public Torch clone() { + return (Torch) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/TrapDoor.java b/src/main/java/org/bukkit/material/TrapDoor.java new file mode 100644 index 00000000..2ae33621 --- /dev/null +++ b/src/main/java/org/bukkit/material/TrapDoor.java @@ -0,0 +1,133 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a trap door + */ +public class TrapDoor extends SimpleAttachableMaterialData implements Openable { + public TrapDoor() { + super(Material.TRAP_DOOR); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public TrapDoor(final int type) { + super(type); + } + + public TrapDoor(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TrapDoor(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TrapDoor(final Material type, final byte data) { + super(type, data); + } + + public boolean isOpen() { + return ((getData() & 0x4) == 0x4); + } + + public void setOpen(boolean isOpen) { + byte data = getData(); + + if (isOpen) { + data |= 0x4; + } else { + data &= ~0x4; + } + + setData(data); + } + + /** + * Test if trapdoor is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set trapdoor inverted state + * + * @param inv - true if inverted (top half), false if normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.NORTH; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.WEST; + } + + return null; + + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0xC); + + switch (face) { + case SOUTH: + data |= 0x1; + break; + case WEST: + data |= 0x2; + break; + case EAST: + data |= 0x3; + break; + } + + setData(data); + } + + @Override + public String toString() { + return (isOpen() ? "OPEN " : "CLOSED ") + super.toString() + " with hinges set " + getAttachedFace() + (isInverted() ? " inverted" : ""); + } + + @Override + public TrapDoor clone() { + return (TrapDoor) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Tree.java b/src/main/java/org/bukkit/material/Tree.java new file mode 100644 index 00000000..e28499d0 --- /dev/null +++ b/src/main/java/org/bukkit/material/Tree.java @@ -0,0 +1,169 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; +import org.bukkit.block.BlockFace; + +/** + * Represents the different types of Tree block that face a direction. + * + * @see Material#LOG + * @see Material#LOG_2 + */ +public class Tree extends Wood { + protected static final Material DEFAULT_TYPE = Material.LOG; + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.UP; + + /** + * Constructs a tree block. + */ + public Tree() { + this(DEFAULT_TYPE, DEFAULT_SPECIES, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given tree species. + * + * @param species the species of the tree block + */ + public Tree(TreeSpecies species) { + this(DEFAULT_TYPE, species, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given tree species, and facing the given + * direction. + * + * @param species the species of the tree block + * @param dir the direction the tree block is facing + */ + public Tree(TreeSpecies species, BlockFace dir) { + this(DEFAULT_TYPE, species, dir); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Tree(final int type) { + super(type); + } + + /** + * Constructs a tree block of the given type. + * + * @param type the type of tree block + */ + public Tree(final Material type) { + this(type, DEFAULT_SPECIES, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given type and tree species. + * + * @param type the type of tree block + * @param species the species of the tree block + */ + public Tree(final Material type, TreeSpecies species) { + this(type, species, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given type and tree species, and facing + * the given direction. + * + * @param type the type of tree block + * @param species the species of the tree block + * @param dir the direction the tree block is facing + */ + public Tree(final Material type, TreeSpecies species, BlockFace dir) { + super(type, species); + setDirection(dir); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Tree(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Tree(final Material type, final byte data) { + super(type, data); + } + + /** + * Get direction of the log + * + * @return one of: + *

      + *
    • BlockFace.TOP for upright (default) + *
    • BlockFace.NORTH (east-west) + *
    • BlockFace.WEST (north-south) + *
    • BlockFace.SELF (directionless) + *
    + */ + @SuppressWarnings("deprecation") + public BlockFace getDirection() { + switch ((getData() >> 2) & 0x3) { + case 0: // Up-down + default: + return BlockFace.UP; + case 1: // North-south + return BlockFace.WEST; + case 2: // East-west + return BlockFace.NORTH; + case 3: // Directionless (bark on all sides) + return BlockFace.SELF; + } + } + + /** + * Set direction of the log + * + * @param dir - direction of end of log (BlockFace.SELF for no direction) + */ + @SuppressWarnings("deprecation") + public void setDirection(BlockFace dir) { + int dat; + switch (dir) { + case UP: + case DOWN: + default: + dat = 0; + break; + case WEST: + case EAST: + dat = 4; // 1<<2 + break; + case NORTH: + case SOUTH: + dat = 8; // 2<<2 + break; + case SELF: + dat = 12; // 3<<2 + break; + } + setData((byte) ((getData() & 0x3) | dat)); + } + + @Override + public String toString() { + return getSpecies() + " " + getDirection() + " " + super.toString(); + } + + @Override + public Tree clone() { + return (Tree) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Tripwire.java b/src/main/java/org/bukkit/material/Tripwire.java new file mode 100644 index 00000000..c95ed999 --- /dev/null +++ b/src/main/java/org/bukkit/material/Tripwire.java @@ -0,0 +1,86 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents the tripwire + */ +public class Tripwire extends MaterialData { + + public Tripwire() { + super(Material.TRIPWIRE); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Tripwire(final int type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Tripwire(final int type, final byte data) { + super(type, data); + } + + /** + * Test if tripwire is currently activated + * + * @return true if activated, false if not + */ + public boolean isActivated() { + return (getData() & 0x4) != 0; + } + + /** + * Set tripwire activated state + * + * @param act - true if activated, false if not + */ + public void setActivated(boolean act) { + int dat = getData() & (0x8 | 0x3); + if (act) { + dat |= 0x4; + } + setData((byte) dat); + } + + /** + * Test if object triggering this tripwire directly + * + * @return true if object activating tripwire, false if not + */ + public boolean isObjectTriggering() { + return (getData() & 0x1) != 0; + } + + /** + * Set object triggering state for this tripwire + * + * @param trig - true if object activating tripwire, false if not + */ + public void setObjectTriggering(boolean trig) { + int dat = getData() & 0xE; + if (trig) { + dat |= 0x1; + } + setData((byte) dat); + } + + @Override + public Tripwire clone() { + return (Tripwire) super.clone(); + } + + @Override + public String toString() { + return super.toString() + (isActivated() ? " Activated" : "") + (isObjectTriggering() ? " Triggered" : ""); + } +} diff --git a/src/main/java/org/bukkit/material/TripwireHook.java b/src/main/java/org/bukkit/material/TripwireHook.java new file mode 100644 index 00000000..72a1fb4f --- /dev/null +++ b/src/main/java/org/bukkit/material/TripwireHook.java @@ -0,0 +1,129 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents the tripwire hook + */ +public class TripwireHook extends SimpleAttachableMaterialData implements Redstone { + + public TripwireHook() { + super(Material.TRIPWIRE_HOOK); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public TripwireHook(final int type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TripwireHook(final int type, final byte data) { + super(type, data); + } + + public TripwireHook(BlockFace dir) { + this(); + setFacingDirection(dir); + } + + /** + * Test if tripwire is connected + * + * @return true if connected, false if not + */ + public boolean isConnected() { + return (getData() & 0x4) != 0; + } + + /** + * Set tripwire connection state + * + * @param connected - true if connected, false if not + */ + public void setConnected(boolean connected) { + int dat = getData() & (0x8 | 0x3); + if (connected) { + dat |= 0x4; + } + setData((byte) dat); + } + + /** + * Test if hook is currently activated + * + * @return true if activated, false if not + */ + public boolean isActivated() { + return (getData() & 0x8) != 0; + } + + /** + * Set hook activated state + * + * @param act - true if activated, false if not + */ + public void setActivated(boolean act) { + int dat = getData() & (0x4 | 0x3); + if (act) { + dat |= 0x8; + } + setData((byte) dat); + } + + public void setFacingDirection(BlockFace face) { + int dat = getData() & 0xC; + switch (face) { + case WEST: + dat |= 0x1; + break; + case NORTH: + dat |= 0x2; + break; + case EAST: + dat |= 0x3; + break; + case SOUTH: + default: + break; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + switch (getData() & 0x3) { + case 0: + return BlockFace.NORTH; + case 1: + return BlockFace.EAST; + case 2: + return BlockFace.SOUTH; + case 3: + return BlockFace.WEST; + } + return null; + } + + public boolean isPowered() { + return isActivated(); + } + + @Override + public TripwireHook clone() { + return (TripwireHook) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + (isActivated() ? " Activated" : "") + (isConnected() ? " Connected" : ""); + } +} diff --git a/src/main/java/org/bukkit/material/Vine.java b/src/main/java/org/bukkit/material/Vine.java new file mode 100644 index 00000000..da84d419 --- /dev/null +++ b/src/main/java/org/bukkit/material/Vine.java @@ -0,0 +1,197 @@ +package org.bukkit.material; + +import java.util.Arrays; +import java.util.EnumSet; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a vine + */ +public class Vine extends MaterialData { + private static final int VINE_NORTH = 0x4; + private static final int VINE_EAST = 0x8; + private static final int VINE_WEST = 0x2; + private static final int VINE_SOUTH = 0x1; + private static final EnumSet possibleFaces = EnumSet.of(BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST); + + public Vine() { + super(Material.VINE); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Vine(int type, byte data) { + super(type, data); + } + + /** + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Vine(byte data) { + super(Material.VINE, data); + } + + public Vine(BlockFace... faces) { + this(EnumSet.copyOf(Arrays.asList(faces))); + } + + public Vine(EnumSet faces) { + this((byte) 0); + faces.retainAll(possibleFaces); + + byte data = 0; + + if (faces.contains(BlockFace.WEST)) { + data |= VINE_WEST; + } + + if (faces.contains(BlockFace.NORTH)) { + data |= VINE_NORTH; + } + + if (faces.contains(BlockFace.SOUTH)) { + data |= VINE_SOUTH; + } + + if (faces.contains(BlockFace.EAST)) { + data |= VINE_EAST; + } + + setData(data); + } + + /** + * Check if the vine is attached to the specified face of an adjacent + * block. You can check two faces at once by passing e.g. {@link + * BlockFace#NORTH_EAST}. + * + * @param face The face to check. + * @return Whether it is attached to that face. + */ + public boolean isOnFace(BlockFace face) { + switch (face) { + case WEST: + return (getData() & VINE_WEST) == VINE_WEST; + case NORTH: + return (getData() & VINE_NORTH) == VINE_NORTH; + case SOUTH: + return (getData() & VINE_SOUTH) == VINE_SOUTH; + case EAST: + return (getData() & VINE_EAST) == VINE_EAST; + case NORTH_EAST: + return isOnFace(BlockFace.EAST) && isOnFace(BlockFace.NORTH); + case NORTH_WEST: + return isOnFace(BlockFace.WEST) && isOnFace(BlockFace.NORTH); + case SOUTH_EAST: + return isOnFace(BlockFace.EAST) && isOnFace(BlockFace.SOUTH); + case SOUTH_WEST: + return isOnFace(BlockFace.WEST) && isOnFace(BlockFace.SOUTH); + case UP: // It's impossible to be accurate with this since it's contextual + return true; + default: + return false; + } + } + + /** + * Attach the vine to the specified face of an adjacent block. + * + * @param face The face to attach. + */ + public void putOnFace(BlockFace face) { + switch (face) { + case WEST: + setData((byte) (getData() | VINE_WEST)); + break; + case NORTH: + setData((byte) (getData() | VINE_NORTH)); + break; + case SOUTH: + setData((byte) (getData() | VINE_SOUTH)); + break; + case EAST: + setData((byte) (getData() | VINE_EAST)); + break; + case NORTH_WEST: + putOnFace(BlockFace.WEST); + putOnFace(BlockFace.NORTH); + break; + case SOUTH_WEST: + putOnFace(BlockFace.WEST); + putOnFace(BlockFace.SOUTH); + break; + case NORTH_EAST: + putOnFace(BlockFace.EAST); + putOnFace(BlockFace.NORTH); + break; + case SOUTH_EAST: + putOnFace(BlockFace.EAST); + putOnFace(BlockFace.SOUTH); + break; + case UP: + break; + default: + throw new IllegalArgumentException("Vines can't go on face " + face.toString()); + } + } + + /** + * Detach the vine from the specified face of an adjacent block. + * + * @param face The face to detach. + */ + public void removeFromFace(BlockFace face) { + switch (face) { + case WEST: + setData((byte) (getData() & ~VINE_WEST)); + break; + case NORTH: + setData((byte) (getData() & ~VINE_NORTH)); + break; + case SOUTH: + setData((byte) (getData() & ~VINE_SOUTH)); + break; + case EAST: + setData((byte) (getData() & ~VINE_EAST)); + break; + case NORTH_WEST: + removeFromFace(BlockFace.WEST); + removeFromFace(BlockFace.NORTH); + break; + case SOUTH_WEST: + removeFromFace(BlockFace.WEST); + removeFromFace(BlockFace.SOUTH); + break; + case NORTH_EAST: + removeFromFace(BlockFace.EAST); + removeFromFace(BlockFace.NORTH); + break; + case SOUTH_EAST: + removeFromFace(BlockFace.EAST); + removeFromFace(BlockFace.SOUTH); + break; + case UP: + break; + default: + throw new IllegalArgumentException("Vines can't go on face " + face.toString()); + } + } + + @Override + public String toString() { + return "VINE"; + } + + @Override + public Vine clone() { + return (Vine) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/Wood.java b/src/main/java/org/bukkit/material/Wood.java new file mode 100644 index 00000000..794cfd4d --- /dev/null +++ b/src/main/java/org/bukkit/material/Wood.java @@ -0,0 +1,196 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents wood blocks of different species. + * + * @see Material#WOOD + * @see Material#SAPLING + * @see Material#WOOD_DOUBLE_STEP + */ +public class Wood extends MaterialData { + protected static final Material DEFAULT_TYPE = Material.WOOD; + protected static final TreeSpecies DEFAULT_SPECIES = TreeSpecies.GENERIC; + + /** + * Constructs a wood block. + */ + public Wood() { + this(DEFAULT_TYPE, DEFAULT_SPECIES); + } + + /** + * Constructs a wood block of the given tree species. + * + * @param species the species of the wood block + */ + public Wood(TreeSpecies species) { + this(DEFAULT_TYPE, species); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Wood(final int type) { + super(type); + } + + /** + * Constructs a wood block of the given type. + * + * @param type the type of wood block + */ + public Wood(final Material type) { + this(type, DEFAULT_SPECIES); + } + + /** + * Constructs a wood block of the given type and tree species. + * + * @param type the type of wood block + * @param species the species of the wood block + */ + public Wood(final Material type, final TreeSpecies species) { + // Ensure only valid species-type combinations + super(getSpeciesType(type, species)); + setSpecies(species); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wood(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wood(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current species of this wood block + * + * @return TreeSpecies of this wood block + */ + public TreeSpecies getSpecies() { + switch (getItemType()) { + case WOOD: + case WOOD_DOUBLE_STEP: + return TreeSpecies.getByData((byte) getData()); + case LOG: + case LEAVES: + return TreeSpecies.getByData((byte) (getData() & 0x3)); + case LOG_2: + case LEAVES_2: + return TreeSpecies.getByData((byte) ((getData() & 0x3) | 0x4)); + case SAPLING: + case WOOD_STEP: + return TreeSpecies.getByData((byte) (getData() & 0x7)); + default: + throw new IllegalArgumentException("Invalid block type for tree species"); + } + } + + /** + * Correct the block type for certain species-type combinations. + * + * @param type The desired type + * @param species The required species + * @return The actual type for this species given the desired type + */ + private static Material getSpeciesType(Material type, TreeSpecies species) { + switch (species) { + case GENERIC: + case REDWOOD: + case BIRCH: + case JUNGLE: + switch (type) { + case LOG_2: + return Material.LOG; + case LEAVES_2: + return Material.LEAVES; + default: + } + break; + case ACACIA: + case DARK_OAK: + switch (type) { + case LOG: + return Material.LOG_2; + case LEAVES: + return Material.LEAVES_2; + default: + } + break; + } + return type; + } + + /** + * Sets the species of this wood block + * + * @param species New species of this wood block + */ + public void setSpecies(final TreeSpecies species) { + boolean firstType = false; + switch (getItemType()) { + case WOOD: + case WOOD_DOUBLE_STEP: + setData(species.getData()); + break; + case LOG: + case LEAVES: + firstType = true; + // fall through to next switch statement below + case LOG_2: + case LEAVES_2: + switch (species) { + case GENERIC: + case REDWOOD: + case BIRCH: + case JUNGLE: + if (!firstType) { + throw new IllegalArgumentException("Invalid tree species for block type, use block type 2 instead"); + } + break; + case ACACIA: + case DARK_OAK: + if (firstType) { + throw new IllegalArgumentException("Invalid tree species for block type 2, use block type instead"); + } + break; + } + setData((byte) ((getData() & 0xC) | (species.getData() & 0x3))); + break; + case SAPLING: + case WOOD_STEP: + setData((byte) ((getData() & 0x8) | species.getData())); + break; + default: + throw new IllegalArgumentException("Invalid block type for tree species"); + } + } + + @Override + public String toString() { + return getSpecies() + " " + super.toString(); + } + + @Override + public Wood clone() { + return (Wood) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/WoodenStep.java b/src/main/java/org/bukkit/material/WoodenStep.java new file mode 100644 index 00000000..17b7050e --- /dev/null +++ b/src/main/java/org/bukkit/material/WoodenStep.java @@ -0,0 +1,106 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of wooden steps. + * + * @see Material#WOOD_STEP + */ +public class WoodenStep extends Wood { + protected static final Material DEFAULT_TYPE = Material.WOOD_STEP; + protected static final boolean DEFAULT_INVERTED = false; + + /** + * Constructs a wooden step. + */ + public WoodenStep() { + this(DEFAULT_SPECIES, DEFAULT_INVERTED); + } + + /** + * Constructs a wooden step of the given tree species. + * + * @param species the species of the wooden step + */ + public WoodenStep(TreeSpecies species) { + this(species, DEFAULT_INVERTED); + } + + /** + * Constructs a wooden step of the given type and tree species, either + * inverted or not. + * + * @param species the species of the wooden step + * @param inv true the step is at the top of the block + */ + public WoodenStep(final TreeSpecies species, boolean inv) { + super(DEFAULT_TYPE, species); + setInverted(inv); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public WoodenStep(final int type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public WoodenStep(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public WoodenStep(final Material type, final byte data) { + super(type, data); + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + @SuppressWarnings("deprecation") + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is normal + * (bottom half) + */ + @SuppressWarnings("deprecation") + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + @Override + public WoodenStep clone() { + return (WoodenStep) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " " + getSpecies() + (isInverted() ? " inverted" : ""); + } +} diff --git a/src/main/java/org/bukkit/material/Wool.java b/src/main/java/org/bukkit/material/Wool.java new file mode 100644 index 00000000..c4e4bfa0 --- /dev/null +++ b/src/main/java/org/bukkit/material/Wool.java @@ -0,0 +1,79 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; +import org.bukkit.Material; + +/** + * Represents a Wool/Cloth block + */ +public class Wool extends MaterialData implements Colorable { + public Wool() { + super(Material.WOOL); + } + + public Wool(DyeColor color) { + this(); + setColor(color); + } + + /** + * @param type the raw type id + * @deprecated Magic value + */ + @Deprecated + public Wool(final int type) { + super(type); + } + + public Wool(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wool(final int type, final byte data) { + super(type, data); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wool(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current color of this dye + * + * @return DyeColor of this dye + */ + public DyeColor getColor() { + return DyeColor.getByWoolData(getData()); + } + + /** + * Sets the color of this dye + * + * @param color New color of this dye + */ + public void setColor(DyeColor color) { + setData(color.getWoolData()); + } + + @Override + public String toString() { + return getColor() + " " + super.toString(); + } + + @Override + public Wool clone() { + return (Wool) super.clone(); + } +} diff --git a/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java b/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java new file mode 100644 index 00000000..6525ab45 --- /dev/null +++ b/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java @@ -0,0 +1,130 @@ +package org.bukkit.material.types; + +import java.util.Map; + +import org.bukkit.block.BlockFace; + +import com.google.common.collect.Maps; + +/** + * Represents the different textured blocks of mushroom. + */ +public enum MushroomBlockTexture { + + /** + * Pores on all faces. + */ + ALL_PORES(0, null), + /** + * Cap texture on the top, north and west faces, pores on remaining sides. + */ + CAP_NORTH_WEST(1, BlockFace.NORTH_WEST), + /** + * Cap texture on the top and north faces, pores on remaining sides. + */ + CAP_NORTH(2, BlockFace.NORTH), + /** + * Cap texture on the top, north and east faces, pores on remaining sides. + */ + CAP_NORTH_EAST(3, BlockFace.NORTH_EAST), + /** + * Cap texture on the top and west faces, pores on remaining sides. + */ + CAP_WEST(4, BlockFace.WEST), + /** + * Cap texture on the top face, pores on remaining sides. + */ + CAP_TOP(5, BlockFace.UP), + /** + * Cap texture on the top and east faces, pores on remaining sides. + */ + CAP_EAST(6, BlockFace.EAST), + /** + * Cap texture on the top, south and west faces, pores on remaining sides. + */ + CAP_SOUTH_WEST(7, BlockFace.SOUTH_WEST), + /** + * Cap texture on the top and south faces, pores on remaining sides. + */ + CAP_SOUTH(8, BlockFace.SOUTH), + /** + * Cap texture on the top, south and east faces, pores on remaining sides. + */ + CAP_SOUTH_EAST(9, BlockFace.SOUTH_EAST), + /** + * Stem texture on the north, east, south and west faces, pores on top and + * bottom. + */ + STEM_SIDES(10, null), + /** + * Cap texture on all faces. + */ + ALL_CAP(14, BlockFace.SELF), + /** + * Stem texture on all faces. + */ + ALL_STEM(15, null); + private final static Map BY_DATA = Maps.newHashMap(); + private final static Map BY_BLOCKFACE = Maps.newHashMap(); + + private final Byte data; + private final BlockFace capFace; + + private MushroomBlockTexture(final int data, final BlockFace capFace) { + this.data = (byte) data; + this.capFace = capFace; + } + + /** + * Gets the associated data value representing this mushroom block face. + * + * @return A byte containing the data value of this mushroom block face + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the face that has cap texture. + * + * @return The cap face + */ + public BlockFace getCapFace() { + return capFace; + } + + /** + * Gets the MushroomBlockType with the given data value. + * + * @param data Data value to fetch + * @return The {@link MushroomBlockTexture} representing the given value, or + * null if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + public static MushroomBlockTexture getByData(final byte data) { + return BY_DATA.get(data); + } + + /** + * Gets the MushroomBlockType with cap texture on the given block face. + * + * @param face the required block face with cap texture + * @return The {@link MushroomBlockTexture} representing the given block + * face, or null if it doesn't exist + * + * @see BlockFace + */ + public static MushroomBlockTexture getCapByFace(final BlockFace face) { + return BY_BLOCKFACE.get(face); + } + + static { + for (MushroomBlockTexture type : values()) { + BY_DATA.put(type.data, type); + BY_BLOCKFACE.put(type.capFace, type); + } + } +} diff --git a/src/main/java/org/bukkit/metadata/FixedMetadataValue.java b/src/main/java/org/bukkit/metadata/FixedMetadataValue.java new file mode 100644 index 00000000..5f485739 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/FixedMetadataValue.java @@ -0,0 +1,41 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; + +/** + * A FixedMetadataValue is a special case metadata item that contains the same + * value forever after initialization. Invalidating a FixedMetadataValue has + * no effect. + *

    + * This class extends LazyMetadataValue for historical reasons, even though it + * overrides all the implementation methods. it is possible that in the future + * that the inheritance hierarchy may change. + */ +public class FixedMetadataValue extends LazyMetadataValue { + + /** + * Store the internal value that is represented by this fixed value. + */ + private final Object internalValue; + + /** + * Initializes a FixedMetadataValue with an Object + * + * @param owningPlugin the {@link Plugin} that created this metadata value + * @param value the value assigned to this metadata value + */ + public FixedMetadataValue(Plugin owningPlugin, final Object value) { + super(owningPlugin); + this.internalValue = value; + } + + @Override + public void invalidate() { + + } + + @Override + public Object value() { + return internalValue; + } +} diff --git a/src/main/java/org/bukkit/metadata/LazyMetadataValue.java b/src/main/java/org/bukkit/metadata/LazyMetadataValue.java new file mode 100644 index 00000000..ce1a8c68 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/LazyMetadataValue.java @@ -0,0 +1,122 @@ +package org.bukkit.metadata; + +import java.lang.ref.SoftReference; +import java.util.concurrent.Callable; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.Plugin; + +/** + * The LazyMetadataValue class implements a type of metadata that is not + * computed until another plugin asks for it. + *

    + * By making metadata values lazy, no computation is done by the providing + * plugin until absolutely necessary (if ever). Additionally, + * LazyMetadataValue objects cache their values internally unless overridden + * by a {@link CacheStrategy} or invalidated at the individual or plugin + * level. Once invalidated, the LazyMetadataValue will recompute its value + * when asked. + */ +public class LazyMetadataValue extends MetadataValueAdapter { + private Callable lazyValue; + private CacheStrategy cacheStrategy; + private SoftReference internalValue; + private static final Object ACTUALLY_NULL = new Object(); + + /** + * Initialized a LazyMetadataValue object with the default + * CACHE_AFTER_FIRST_EVAL cache strategy. + * + * @param owningPlugin the {@link Plugin} that created this metadata + * value. + * @param lazyValue the lazy value assigned to this metadata value. + */ + public LazyMetadataValue(Plugin owningPlugin, Callable lazyValue) { + this(owningPlugin, CacheStrategy.CACHE_AFTER_FIRST_EVAL, lazyValue); + } + + /** + * Initializes a LazyMetadataValue object with a specific cache strategy. + * + * @param owningPlugin the {@link Plugin} that created this metadata + * value. + * @param cacheStrategy determines the rules for caching this metadata + * value. + * @param lazyValue the lazy value assigned to this metadata value. + */ + public LazyMetadataValue(Plugin owningPlugin, CacheStrategy cacheStrategy, Callable lazyValue) { + super(owningPlugin); + Validate.notNull(cacheStrategy, "cacheStrategy cannot be null"); + Validate.notNull(lazyValue, "lazyValue cannot be null"); + this.internalValue = new SoftReference(null); + this.lazyValue = lazyValue; + this.cacheStrategy = cacheStrategy; + } + + /** + * Protected special constructor used by FixedMetadataValue to bypass + * standard setup. + * + * @param owningPlugin the owning plugin + */ + protected LazyMetadataValue(Plugin owningPlugin) { + super(owningPlugin); + } + + public Object value() { + eval(); + Object value = internalValue.get(); + if (value == ACTUALLY_NULL) { + return null; + } + return value; + } + + /** + * Lazily evaluates the value of this metadata item. + * + * @throws MetadataEvaluationException if computing the metadata value + * fails. + */ + private synchronized void eval() throws MetadataEvaluationException { + if (cacheStrategy == CacheStrategy.NEVER_CACHE || internalValue.get() == null) { + try { + Object value = lazyValue.call(); + if (value == null) { + value = ACTUALLY_NULL; + } + internalValue = new SoftReference(value); + } catch (Exception e) { + throw new MetadataEvaluationException(e); + } + } + } + + public synchronized void invalidate() { + if (cacheStrategy != CacheStrategy.CACHE_ETERNALLY) { + internalValue.clear(); + } + } + + /** + * Describes possible caching strategies for metadata. + */ + public enum CacheStrategy { + /** + * Once the metadata value has been evaluated, do not re-evaluate the + * value until it is manually invalidated. + */ + CACHE_AFTER_FIRST_EVAL, + + /** + * Re-evaluate the metadata item every time it is requested + */ + NEVER_CACHE, + + /** + * Once the metadata value has been evaluated, do not re-evaluate the + * value in spite of manual invalidation. + */ + CACHE_ETERNALLY + } +} diff --git a/src/main/java/org/bukkit/metadata/MetadataConversionException.java b/src/main/java/org/bukkit/metadata/MetadataConversionException.java new file mode 100644 index 00000000..a3def46a --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataConversionException.java @@ -0,0 +1,13 @@ +package org.bukkit.metadata; + +/** + * A MetadataConversionException is thrown any time a {@link + * LazyMetadataValue} attempts to convert a metadata value to an inappropriate + * data type. + */ +@SuppressWarnings("serial") +public class MetadataConversionException extends RuntimeException { + MetadataConversionException(String message) { + super(message); + } +} diff --git a/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java b/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java new file mode 100644 index 00000000..918e7c83 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java @@ -0,0 +1,13 @@ +package org.bukkit.metadata; + +/** + * A MetadataEvaluationException is thrown any time a {@link + * LazyMetadataValue} fails to evaluate its value due to an exception. The + * originating exception will be included as this exception's cause. + */ +@SuppressWarnings("serial") +public class MetadataEvaluationException extends RuntimeException { + MetadataEvaluationException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/bukkit/metadata/MetadataStore.java b/src/main/java/org/bukkit/metadata/MetadataStore.java new file mode 100644 index 00000000..700d0bfb --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataStore.java @@ -0,0 +1,60 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; + +import java.util.List; + +public interface MetadataStore { + /** + * Adds a metadata value to an object. + * + * @param subject The object receiving the metadata. + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue); + + /** + * Returns all metadata values attached to an object. If multiple plugins + * have attached metadata, each will value will be included. + * + * @param subject the object being interrogated. + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + */ + public List getMetadata(T subject, String metadataKey); + + /** + * Tests to see if a metadata attribute has been set on an object. + * + * @param subject the object upon which the has-metadata test is + * performed. + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public boolean hasMetadata(T subject, String metadataKey); + + /** + * Removes a metadata item owned by a plugin from a subject. + * + * @param subject the object to remove the metadata from. + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin the plugin attempting to remove a metadata item. + * @throws IllegalArgumentException If plugin is null + */ + public void removeMetadata(T subject, String metadataKey, Plugin owningPlugin); + + /** + * Invalidates all metadata in the metadata store that originates from the + * given plugin. Doing this will force each invalidated metadata item to + * be recalculated the next time it is accessed. + * + * @param owningPlugin the plugin requesting the invalidation. + * @throws IllegalArgumentException If plugin is null + */ + public void invalidateAll(Plugin owningPlugin); +} diff --git a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java new file mode 100644 index 00000000..e9bd9fb1 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java @@ -0,0 +1,136 @@ +package org.bukkit.metadata; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.Plugin; + +import java.util.*; + +public abstract class MetadataStoreBase { + private Map> metadataMap = new HashMap>(); + + /** + * Adds a metadata value to an object. Each metadata value is owned by a + * specific {@link Plugin}. If a plugin has already added a metadata value + * to an object, that value will be replaced with the value of {@code + * newMetadataValue}. Multiple plugins can set independent values for the + * same {@code metadataKey} without conflict. + *

    + * Implementation note: I considered using a {@link + * java.util.concurrent.locks.ReadWriteLock} for controlling access to + * {@code metadataMap}, but decided that the added overhead wasn't worth + * the finer grained access control. + *

    + * Bukkit is almost entirely single threaded so locking overhead shouldn't + * pose a problem. + * + * @param subject The object receiving the metadata. + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @see MetadataStore#setMetadata(Object, String, MetadataValue) + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public synchronized void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue) { + Validate.notNull(newMetadataValue, "Value cannot be null"); + Plugin owningPlugin = newMetadataValue.getOwningPlugin(); + Validate.notNull(owningPlugin, "Plugin cannot be null"); + String key = disambiguate(subject, metadataKey); + Map entry = metadataMap.get(key); + if (entry == null) { + entry = new WeakHashMap(1); + metadataMap.put(key, entry); + } + entry.put(owningPlugin, newMetadataValue); + } + + /** + * Returns all metadata values attached to an object. If multiple + * have attached metadata, each will value will be included. + * + * @param subject the object being interrogated. + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + * @see MetadataStore#getMetadata(Object, String) + */ + public synchronized List getMetadata(T subject, String metadataKey) { + String key = disambiguate(subject, metadataKey); + if (metadataMap.containsKey(key)) { + Collection values = metadataMap.get(key).values(); + return Collections.unmodifiableList(new ArrayList(values)); + } else { + return Collections.emptyList(); + } + } + + /** + * Tests to see if a metadata attribute has been set on an object. + * + * @param subject the object upon which the has-metadata test is + * performed. + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public synchronized boolean hasMetadata(T subject, String metadataKey) { + String key = disambiguate(subject, metadataKey); + return metadataMap.containsKey(key); + } + + /** + * Removes a metadata item owned by a plugin from a subject. + * + * @param subject the object to remove the metadata from. + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin the plugin attempting to remove a metadata item. + * @see MetadataStore#removeMetadata(Object, String, + * Plugin) + * @throws IllegalArgumentException If plugin is null + */ + public synchronized void removeMetadata(T subject, String metadataKey, Plugin owningPlugin) { + Validate.notNull(owningPlugin, "Plugin cannot be null"); + String key = disambiguate(subject, metadataKey); + Map entry = metadataMap.get(key); + if (entry == null) { + return; + } + + entry.remove(owningPlugin); + if (entry.isEmpty()) { + metadataMap.remove(key); + } + } + + /** + * Invalidates all metadata in the metadata store that originates from the + * given plugin. Doing this will force each invalidated metadata item to + * be recalculated the next time it is accessed. + * + * @param owningPlugin the plugin requesting the invalidation. + * @see MetadataStore#invalidateAll(Plugin) + * @throws IllegalArgumentException If plugin is null + */ + public synchronized void invalidateAll(Plugin owningPlugin) { + Validate.notNull(owningPlugin, "Plugin cannot be null"); + for (Map values : metadataMap.values()) { + if (values.containsKey(owningPlugin)) { + values.get(owningPlugin).invalidate(); + } + } + } + + /** + * Creates a unique name for the object receiving metadata by combining + * unique data from the subject with a metadataKey. + *

    + * The name created must be globally unique for the given object and any + * two equivalent objects must generate the same unique name. For example, + * two Player objects must generate the same string if they represent the + * same player, even if the objects would fail a reference equality test. + * + * @param subject The object for which this key is being generated. + * @param metadataKey The name identifying the metadata value. + * @return a unique metadata key for the given subject. + */ + protected abstract String disambiguate(T subject, String metadataKey); +} diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java new file mode 100644 index 00000000..eded8c04 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataValue.java @@ -0,0 +1,83 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; + +public interface MetadataValue { + + /** + * Fetches the value of this metadata item. + * + * @return the metadata value. + */ + public Object value(); + + /** + * Attempts to convert the value of this metadata item into an int. + * + * @return the value as an int. + */ + public int asInt(); + + /** + * Attempts to convert the value of this metadata item into a float. + * + * @return the value as a float. + */ + public float asFloat(); + + /** + * Attempts to convert the value of this metadata item into a double. + * + * @return the value as a double. + */ + public double asDouble(); + + /** + * Attempts to convert the value of this metadata item into a long. + * + * @return the value as a long. + */ + public long asLong(); + + /** + * Attempts to convert the value of this metadata item into a short. + * + * @return the value as a short. + */ + public short asShort(); + + /** + * Attempts to convert the value of this metadata item into a byte. + * + * @return the value as a byte. + */ + public byte asByte(); + + /** + * Attempts to convert the value of this metadata item into a boolean. + * + * @return the value as a boolean. + */ + public boolean asBoolean(); + + /** + * Attempts to convert the value of this metadata item into a string. + * + * @return the value as a string. + */ + public String asString(); + + /** + * Returns the {@link Plugin} that created this metadata item. + * + * @return the plugin that owns this metadata value. This should never be + * null. + */ + public Plugin getOwningPlugin(); + + /** + * Invalidates this metadata item, forcing it to recompute when next + * accessed. + */ + public void invalidate(); +} diff --git a/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java b/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java new file mode 100644 index 00000000..5e6df440 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java @@ -0,0 +1,78 @@ +package org.bukkit.metadata; + +import java.lang.ref.WeakReference; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.NumberConversions; + +/** + * Optional base class for facilitating MetadataValue implementations. + *

    + * This provides all the conversion functions for MetadataValue so that + * writing an implementation of MetadataValue is as simple as implementing + * value() and invalidate(). + */ +public abstract class MetadataValueAdapter implements MetadataValue { + protected final WeakReference owningPlugin; + + protected MetadataValueAdapter(Plugin owningPlugin) { + Validate.notNull(owningPlugin, "owningPlugin cannot be null"); + this.owningPlugin = new WeakReference(owningPlugin); + } + + public Plugin getOwningPlugin() { + return owningPlugin.get(); + } + + public int asInt() { + return NumberConversions.toInt(value()); + } + + public float asFloat() { + return NumberConversions.toFloat(value()); + } + + public double asDouble() { + return NumberConversions.toDouble(value()); + } + + public long asLong() { + return NumberConversions.toLong(value()); + } + + public short asShort() { + return NumberConversions.toShort(value()); + } + + public byte asByte() { + return NumberConversions.toByte(value()); + } + + public boolean asBoolean() { + Object value = value(); + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } + + if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } + + return value != null; + } + + public String asString() { + Object value = value(); + + if (value == null) { + return ""; + } + return value.toString(); + } + +} diff --git a/src/main/java/org/bukkit/metadata/Metadatable.java b/src/main/java/org/bukkit/metadata/Metadatable.java new file mode 100644 index 00000000..b47cf2b0 --- /dev/null +++ b/src/main/java/org/bukkit/metadata/Metadatable.java @@ -0,0 +1,52 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; + +import java.util.List; + +/** + * This interface is implemented by all objects that can provide metadata + * about themselves. + */ +public interface Metadatable { + /** + * Sets a metadata value in the implementing object's metadata store. + * + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public void setMetadata(String metadataKey, MetadataValue newMetadataValue); + + /** + * Returns a list of previously set metadata values from the implementing + * object's metadata store. + * + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + */ + public List getMetadata(String metadataKey); + + /** + * Tests to see whether the implementing object contains the given + * metadata value in its metadata store. + * + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public boolean hasMetadata(String metadataKey); + + /** + * Removes the given metadata value from the implementing object's + * metadata store. + * + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin This plugin's metadata value will be removed. All + * other values will be left untouched. + * @throws IllegalArgumentException If plugin is null + */ + public void removeMetadata(String metadataKey, Plugin owningPlugin); +} diff --git a/src/main/java/org/bukkit/permissions/Permissible.java b/src/main/java/org/bukkit/permissions/Permissible.java new file mode 100644 index 00000000..5cd3cff0 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/Permissible.java @@ -0,0 +1,122 @@ +package org.bukkit.permissions; + +import java.util.Set; +import org.bukkit.plugin.Plugin; + +/** + * Represents an object that may be assigned permissions + */ +public interface Permissible extends ServerOperator { + + /** + * Checks if this object contains an override for the specified + * permission, by fully qualified name + * + * @param name Name of the permission + * @return true if the permission is set, otherwise false + */ + public boolean isPermissionSet(String name); + + /** + * Checks if this object contains an override for the specified {@link + * Permission} + * + * @param perm Permission to check + * @return true if the permission is set, otherwise false + */ + public boolean isPermissionSet(Permission perm); + + /** + * Gets the value of the specified permission, if set. + *

    + * If a permission override is not set on this object, the default value + * of the permission will be returned. + * + * @param name Name of the permission + * @return Value of the permission + */ + public boolean hasPermission(String name); + + /** + * Gets the value of the specified permission, if set. + *

    + * If a permission override is not set on this object, the default value + * of the permission will be returned + * + * @param perm Permission to get + * @return Value of the permission + */ + public boolean hasPermission(Permission perm); + + /** + * Adds a new {@link PermissionAttachment} with a single permission by + * name and value + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param name Name of the permission to attach + * @param value Value of the permission + * @return The PermissionAttachment that was just created + */ + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value); + + /** + * Adds a new empty {@link PermissionAttachment} to this object + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @return The PermissionAttachment that was just created + */ + public PermissionAttachment addAttachment(Plugin plugin); + + /** + * Temporarily adds a new {@link PermissionAttachment} with a single + * permission by name and value + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param name Name of the permission to attach + * @param value Value of the permission + * @param ticks Amount of ticks to automatically remove this attachment + * after + * @return The PermissionAttachment that was just created + */ + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks); + + /** + * Temporarily adds a new empty {@link PermissionAttachment} to this + * object + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param ticks Amount of ticks to automatically remove this attachment + * after + * @return The PermissionAttachment that was just created + */ + public PermissionAttachment addAttachment(Plugin plugin, int ticks); + + /** + * Removes the given {@link PermissionAttachment} from this object + * + * @param attachment Attachment to remove + * @throws IllegalArgumentException Thrown when the specified attachment + * isn't part of this object + */ + public void removeAttachment(PermissionAttachment attachment); + + /** + * Recalculates the permissions for this object, if the attachments have + * changed values. + *

    + * This should very rarely need to be called from a plugin. + */ + public void recalculatePermissions(); + + /** + * Gets a set containing all of the permissions currently in effect by + * this object + * + * @return Set of currently effective permissions + */ + public Set getEffectivePermissions(); +} diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java new file mode 100644 index 00000000..eb0a55c5 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java @@ -0,0 +1,252 @@ +package org.bukkit.permissions; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +/** + * Base Permissible for use in any Permissible object via proxy or extension + */ +public class PermissibleBase implements Permissible { + private ServerOperator opable = null; + private Permissible parent = this; + private final List attachments = new LinkedList(); + private final Map permissions = new HashMap(); + + public PermissibleBase(ServerOperator opable) { + this.opable = opable; + + if (opable instanceof Permissible) { + this.parent = (Permissible) opable; + } + + recalculatePermissions(); + } + + public boolean isOp() { + if (opable == null) { + return false; + } else { + return opable.isOp(); + } + } + + public void setOp(boolean value) { + if (opable == null) { + throw new UnsupportedOperationException("Cannot change op value as no ServerOperator is set"); + } else { + opable.setOp(value); + } + } + + public boolean isPermissionSet(String name) { + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } + + return permissions.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public boolean isPermissionSet(Permission perm) { + if (perm == null) { + throw new IllegalArgumentException("Permission cannot be null"); + } + + return isPermissionSet(perm.getName()); + } + + public boolean hasPermission(String inName) { + if (inName == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } + + String name = inName.toLowerCase(java.util.Locale.ENGLISH); + + // Paper start + PermissionAttachmentInfo info = permissions.get(name); + if (info != null) { + return info.getValue(); + // Paper end + } else { + Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); + + if (perm != null) { + return perm.getDefault().getValue(isOp()); + } else { + return Permission.DEFAULT_PERMISSION.getValue(isOp()); + } + } + } + + public boolean hasPermission(Permission perm) { + if (perm == null) { + throw new IllegalArgumentException("Permission cannot be null"); + } + + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + // Paper start + PermissionAttachmentInfo info = permissions.get(name); + if (info != null) { + return info.getValue(); + } + // Paper end + return perm.getDefault().getValue(isOp()); + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin); + result.setPermission(name, value); + + recalculatePermissions(); + + return result; + } + + public PermissionAttachment addAttachment(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = new PermissionAttachment(plugin, parent); + + attachments.add(result); + recalculatePermissions(); + + return result; + } + + public void removeAttachment(PermissionAttachment attachment) { + if (attachment == null) { + throw new IllegalArgumentException("Attachment cannot be null"); + } + + if (attachments.contains(attachment)) { + attachments.remove(attachment); + PermissionRemovedExecutor ex = attachment.getRemovalCallback(); + + if (ex != null) { + ex.attachmentRemoved(attachment); + } + + recalculatePermissions(); + } else { + throw new IllegalArgumentException("Given attachment is not part of Permissible object " + parent); + } + } + + public void recalculatePermissions() { + clearPermissions(); + Set defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(isOp()); + Bukkit.getServer().getPluginManager().subscribeToDefaultPerms(isOp(), parent); + + for (Permission perm : defaults) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + permissions.put(name, new PermissionAttachmentInfo(parent, name, null, true)); + Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); + calculateChildPermissions(perm.getChildren(), false, null); + } + + for (PermissionAttachment attachment : attachments) { + calculateChildPermissions(attachment.getPermissions(), false, attachment); + } + } + + public synchronized void clearPermissions() { + Set perms = permissions.keySet(); + + for (String name : perms) { + Bukkit.getServer().getPluginManager().unsubscribeFromPermission(name, parent); + } + + Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(false, parent); + Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(true, parent); + + permissions.clear(); + } + + private void calculateChildPermissions(Map children, boolean invert, PermissionAttachment attachment) { + for (Map.Entry entry : children.entrySet()) { + String name = entry.getKey(); + + Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); + boolean value = entry.getValue() ^ invert; + String lname = name.toLowerCase(java.util.Locale.ENGLISH); + + permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value)); + Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); + + if (perm != null) { + calculateChildPermissions(perm.getChildren(), !value, attachment); + } + } + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin, ticks); + + if (result != null) { + result.setPermission(name, value); + } + + return result; + } + + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin); + + if (Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new RemoveAttachmentRunnable(result), ticks) == -1) { + Bukkit.getServer().getLogger().log(Level.WARNING, "Could not add PermissionAttachment to " + parent + " for plugin " + plugin.getDescription().getFullName() + ": Scheduler returned -1"); + result.remove(); + return null; + } else { + return result; + } + } + + public Set getEffectivePermissions() { + return new HashSet(permissions.values()); + } + + private static class RemoveAttachmentRunnable implements Runnable { + private PermissionAttachment attachment; + + public RemoveAttachmentRunnable(PermissionAttachment attachment) { + this.attachment = attachment; + } + + public void run() { + attachment.remove(); + } + } +} diff --git a/src/main/java/org/bukkit/permissions/Permission.java b/src/main/java/org/bukkit/permissions/Permission.java new file mode 100644 index 00000000..7f6df876 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/Permission.java @@ -0,0 +1,345 @@ +package org.bukkit.permissions; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; + +/** + * Represents a unique permission that may be attached to a {@link + * Permissible} + */ +public class Permission { + public static final PermissionDefault DEFAULT_PERMISSION = PermissionDefault.OP; + + private final String name; + private final Map children = new LinkedHashMap(); + private PermissionDefault defaultValue = DEFAULT_PERMISSION; + private String description; + + public Permission(String name) { + this(name, null, null, null); + } + + public Permission(String name, String description) { + this(name, description, null, null); + } + + public Permission(String name, PermissionDefault defaultValue) { + this(name, null, defaultValue, null); + } + + public Permission(String name, String description, PermissionDefault defaultValue) { + this(name, description, defaultValue, null); + } + + public Permission(String name, Map children) { + this(name, null, null, children); + } + + public Permission(String name, String description, Map children) { + this(name, description, null, children); + } + + public Permission(String name, PermissionDefault defaultValue, Map children) { + this(name, null, defaultValue, children); + } + + public Permission(String name, String description, PermissionDefault defaultValue, Map children) { + Validate.notNull(name, "Name cannot be null"); + this.name = name; + this.description = (description == null) ? "" : description; + + if (defaultValue != null) { + this.defaultValue = defaultValue; + } + + if (children != null) { + this.children.putAll(children); + } + + recalculatePermissibles(); + } + + /** + * Returns the unique fully qualified name of this Permission + * + * @return Fully qualified name + */ + public String getName() { + return name; + } + + /** + * Gets the children of this permission. + *

    + * If you change this map in any form, you must call {@link + * #recalculatePermissibles()} to recalculate all {@link Permissible}s + * + * @return Permission children + */ + public Map getChildren() { + return children; + } + + /** + * Gets the default value of this permission. + * + * @return Default value of this permission. + */ + public PermissionDefault getDefault() { + return defaultValue; + } + + /** + * Sets the default value of this permission. + *

    + * This will not be saved to disk, and is a temporary operation until the + * server reloads permissions. Changing this default will cause all {@link + * Permissible}s that contain this permission to recalculate their + * permissions + * + * @param value The new default to set + */ + public void setDefault(PermissionDefault value) { + if (defaultValue == null) { + throw new IllegalArgumentException("Default value cannot be null"); + } + + defaultValue = value; + recalculatePermissibles(); + } + + /** + * Gets a brief description of this permission, if set + * + * @return Brief description of this permission + */ + public String getDescription() { + return description; + } + + /** + * Sets the description of this permission. + *

    + * This will not be saved to disk, and is a temporary operation until the + * server reloads permissions. + * + * @param value The new description to set + */ + public void setDescription(String value) { + if (value == null) { + description = ""; + } else { + description = value; + } + } + + /** + * Gets a set containing every {@link Permissible} that has this + * permission. + *

    + * This set cannot be modified. + * + * @return Set containing permissibles with this permission + */ + public Set getPermissibles() { + return Bukkit.getServer().getPluginManager().getPermissionSubscriptions(name); + } + + /** + * Recalculates all {@link Permissible}s that contain this permission. + *

    + * This should be called after modifying the children, and is + * automatically called after modifying the default value + */ + public void recalculatePermissibles() { + Set perms = getPermissibles(); + + Bukkit.getServer().getPluginManager().recalculatePermissionDefaults(this); + + for (Permissible p : perms) { + p.recalculatePermissions(); + } + } + + /** + * Adds this permission to the specified parent permission. + *

    + * If the parent permission does not exist, it will be created and + * registered. + * + * @param name Name of the parent permission + * @param value The value to set this permission to + * @return Parent permission it created or loaded + */ + public Permission addParent(String name, boolean value) { + PluginManager pm = Bukkit.getServer().getPluginManager(); + String lname = name.toLowerCase(java.util.Locale.ENGLISH); + + Permission perm = pm.getPermission(lname); + + if (perm == null) { + perm = new Permission(lname); + pm.addPermission(perm); + } + + addParent(perm, value); + + return perm; + } + + /** + * Adds this permission to the specified parent permission. + * + * @param perm Parent permission to register with + * @param value The value to set this permission to + */ + public void addParent(Permission perm, boolean value) { + perm.getChildren().put(getName(), value); + perm.recalculatePermissibles(); + } + + /** + * Loads a list of Permissions from a map of data, usually used from + * retrieval from a yaml file. + *

    + * The data may contain a list of name:data, where the data contains the + * following keys: + *

      + *
    • default: Boolean true or false. If not specified, false. + *
    • children: {@code Map} of child permissions. If not + * specified, empty list. + *
    • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
    + * + * @param data Map of permissions + * @param error An error message to show if a permission is invalid. + * @param def Default permission value to use if missing + * @return Permission object + */ + public static List loadPermissions(Map data, String error, PermissionDefault def) { + List result = new ArrayList(); + + for (Map.Entry entry : data.entrySet()) { + try { + result.add(Permission.loadPermission(entry.getKey().toString(), (Map) entry.getValue(), def, result)); + } catch (Throwable ex) { + Bukkit.getServer().getLogger().log(Level.SEVERE, String.format(error, entry.getKey()), ex); + } + } + + return result; + } + + /** + * Loads a Permission from a map of data, usually used from retrieval from + * a yaml file. + *

    + * The data may contain the following keys: + *

      + *
    • default: Boolean true or false. If not specified, false. + *
    • children: {@code Map} of child permissions. If not + * specified, empty list. + *
    • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
    + * + * @param name Name of the permission + * @param data Map of keys + * @return Permission object + */ + public static Permission loadPermission(String name, Map data) { + return loadPermission(name, data, DEFAULT_PERMISSION, null); + } + + /** + * Loads a Permission from a map of data, usually used from retrieval from + * a yaml file. + *

    + * The data may contain the following keys: + *

      + *
    • default: Boolean true or false. If not specified, false. + *
    • children: {@code Map} of child permissions. If not + * specified, empty list. + *
    • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
    + * + * @param name Name of the permission + * @param data Map of keys + * @param def Default permission value to use if not set + * @param output A list to append any created child-Permissions to, may be null + * @return Permission object + */ + public static Permission loadPermission(String name, Map data, PermissionDefault def, List output) { + Validate.notNull(name, "Name cannot be null"); + Validate.notNull(data, "Data cannot be null"); + + String desc = null; + Map children = null; + + if (data.get("default") != null) { + PermissionDefault value = PermissionDefault.getByName(data.get("default").toString()); + if (value != null) { + def = value; + } else { + throw new IllegalArgumentException("'default' key contained unknown value"); + } + } + + if (data.get("children") != null) { + Object childrenNode = data.get("children"); + if (childrenNode instanceof Iterable) { + children = new LinkedHashMap(); + for (Object child : (Iterable) childrenNode) { + if (child != null) { + children.put(child.toString(), Boolean.TRUE); + } + } + } else if (childrenNode instanceof Map) { + children = extractChildren((Map) childrenNode, name, def, output); + } else { + throw new IllegalArgumentException("'children' key is of wrong type"); + } + } + + if (data.get("description") != null) { + desc = data.get("description").toString(); + } + + return new Permission(name, desc, def, children); + } + + private static Map extractChildren(Map input, String name, PermissionDefault def, List output) { + Map children = new LinkedHashMap(); + + for (Map.Entry entry : input.entrySet()) { + if ((entry.getValue() instanceof Boolean)) { + children.put(entry.getKey().toString(), (Boolean) entry.getValue()); + } else if ((entry.getValue() instanceof Map)) { + try { + Permission perm = loadPermission(entry.getKey().toString(), (Map) entry.getValue(), def, output); + children.put(perm.getName(), Boolean.TRUE); + + if (output != null) { + output.add(perm); + } + } catch (Throwable ex) { + throw new IllegalArgumentException("Permission node '" + entry.getKey().toString() + "' in child of " + name + " is invalid", ex); + } + } else { + throw new IllegalArgumentException("Child '" + entry.getKey().toString() + "' contains invalid value"); + } + } + + return children; + } +} diff --git a/src/main/java/org/bukkit/permissions/PermissionAttachment.java b/src/main/java/org/bukkit/permissions/PermissionAttachment.java new file mode 100644 index 00000000..9849261d --- /dev/null +++ b/src/main/java/org/bukkit/permissions/PermissionAttachment.java @@ -0,0 +1,139 @@ +package org.bukkit.permissions; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.bukkit.plugin.Plugin; + +/** + * Holds information about a permission attachment on a {@link Permissible} + * object + */ +public class PermissionAttachment { + private PermissionRemovedExecutor removed; + private final Map permissions = new LinkedHashMap(); + private final Permissible permissible; + private final Plugin plugin; + + public PermissionAttachment(Plugin plugin, Permissible Permissible) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + this.permissible = Permissible; + this.plugin = plugin; + } + + /** + * Gets the plugin responsible for this attachment + * + * @return Plugin responsible for this permission attachment + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Sets an object to be called for when this attachment is removed from a + * {@link Permissible}. May be null. + * + * @param ex Object to be called when this is removed + */ + public void setRemovalCallback(PermissionRemovedExecutor ex) { + removed = ex; + } + + /** + * Gets the class that was previously set to be called when this + * attachment was removed from a {@link Permissible}. May be null. + * + * @return Object to be called when this is removed + */ + public PermissionRemovedExecutor getRemovalCallback() { + return removed; + } + + /** + * Gets the Permissible that this is attached to + * + * @return Permissible containing this attachment + */ + public Permissible getPermissible() { + return permissible; + } + + /** + * Gets a copy of all set permissions and values contained within this + * attachment. + *

    + * This map may be modified but will not affect the attachment, as it is a + * copy. + * + * @return Copy of all permissions and values expressed by this attachment + */ + public Map getPermissions() { + return new LinkedHashMap(permissions); + } + + /** + * Sets a permission to the given value, by its fully qualified name + * + * @param name Name of the permission + * @param value New value of the permission + */ + public void setPermission(String name, boolean value) { + permissions.put(name.toLowerCase(java.util.Locale.ENGLISH), value); + permissible.recalculatePermissions(); + } + + /** + * Sets a permission to the given value + * + * @param perm Permission to set + * @param value New value of the permission + */ + public void setPermission(Permission perm, boolean value) { + setPermission(perm.getName(), value); + } + + /** + * Removes the specified permission from this attachment. + *

    + * If the permission does not exist in this attachment, nothing will + * happen. + * + * @param name Name of the permission to remove + */ + public void unsetPermission(String name) { + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + permissible.recalculatePermissions(); + } + + /** + * Removes the specified permission from this attachment. + *

    + * If the permission does not exist in this attachment, nothing will + * happen. + * + * @param perm Permission to remove + */ + public void unsetPermission(Permission perm) { + unsetPermission(perm.getName()); + } + + /** + * Removes this attachment from its registered {@link Permissible} + * + * @return true if the permissible was removed successfully, false if it + * did not exist + */ + public boolean remove() { + try { + permissible.removeAttachment(this); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } +} diff --git a/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java b/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java new file mode 100644 index 00000000..8e8e3355 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java @@ -0,0 +1,62 @@ +package org.bukkit.permissions; + +/** + * Holds information on a permission and which {@link PermissionAttachment} + * provides it + */ +public class PermissionAttachmentInfo { + private final Permissible permissible; + private final String permission; + private final PermissionAttachment attachment; + private final boolean value; + + public PermissionAttachmentInfo(Permissible permissible, String permission, PermissionAttachment attachment, boolean value) { + if (permissible == null) { + throw new IllegalArgumentException("Permissible may not be null"); + } else if (permission == null) { + throw new IllegalArgumentException("Permissions may not be null"); + } + + this.permissible = permissible; + this.permission = permission; + this.attachment = attachment; + this.value = value; + } + + /** + * Gets the permissible this is attached to + * + * @return Permissible this permission is for + */ + public Permissible getPermissible() { + return permissible; + } + + /** + * Gets the permission being set + * + * @return Name of the permission + */ + public String getPermission() { + return permission; + } + + /** + * Gets the attachment providing this permission. This may be null for + * default permissions (usually parent permissions). + * + * @return Attachment + */ + public PermissionAttachment getAttachment() { + return attachment; + } + + /** + * Gets the value of this permission + * + * @return Value of the permission + */ + public boolean getValue() { + return value; + } +} diff --git a/src/main/java/org/bukkit/permissions/PermissionDefault.java b/src/main/java/org/bukkit/permissions/PermissionDefault.java new file mode 100644 index 00000000..00f08b3b --- /dev/null +++ b/src/main/java/org/bukkit/permissions/PermissionDefault.java @@ -0,0 +1,66 @@ +package org.bukkit.permissions; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the possible default values for permissions + */ +public enum PermissionDefault { + TRUE("true"), + FALSE("false"), + OP("op", "isop", "operator", "isoperator", "admin", "isadmin"), + NOT_OP("!op", "notop", "!operator", "notoperator", "!admin", "notadmin"); + + private final String[] names; + private final static Map lookup = new HashMap(); + + private PermissionDefault(String... names) { + this.names = names; + } + + /** + * Calculates the value of this PermissionDefault for the given operator + * value + * + * @param op If the target is op + * @return True if the default should be true, or false + */ + public boolean getValue(boolean op) { + switch (this) { + case TRUE: + return true; + case FALSE: + return false; + case OP: + return op; + case NOT_OP: + return !op; + default: + return false; + } + } + + /** + * Looks up a PermissionDefault by name + * + * @param name Name of the default + * @return Specified value, or null if not found + */ + public static PermissionDefault getByName(String name) { + return lookup.get(name.toLowerCase(java.util.Locale.ENGLISH).replaceAll("[^a-z!]", "")); + } + + @Override + public String toString() { + return names[0]; + } + + static { + for (PermissionDefault value : values()) { + for (String name : value.names) { + lookup.put(name, value); + } + } + } +} diff --git a/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java b/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java new file mode 100644 index 00000000..b13d0082 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java @@ -0,0 +1,16 @@ +package org.bukkit.permissions; + +/** + * Represents a class which is to be notified when a {@link + * PermissionAttachment} is removed from a {@link Permissible} + */ +public interface PermissionRemovedExecutor { + + /** + * Called when a {@link PermissionAttachment} is removed from a {@link + * Permissible} + * + * @param attachment Attachment which was removed + */ + public void attachmentRemoved(PermissionAttachment attachment); +} diff --git a/src/main/java/org/bukkit/permissions/ServerOperator.java b/src/main/java/org/bukkit/permissions/ServerOperator.java new file mode 100644 index 00000000..26ed2430 --- /dev/null +++ b/src/main/java/org/bukkit/permissions/ServerOperator.java @@ -0,0 +1,24 @@ +package org.bukkit.permissions; + +import org.bukkit.entity.Player; + +/** + * Represents an object that may become a server operator, such as a {@link + * Player} + */ +public interface ServerOperator { + + /** + * Checks if this object is a server operator + * + * @return true if this is an operator, otherwise false + */ + public boolean isOp(); + + /** + * Sets the operator status of this object + * + * @param value New operator value + */ + public void setOp(boolean value); +} diff --git a/src/main/java/org/bukkit/plugin/AuthorNagException.java b/src/main/java/org/bukkit/plugin/AuthorNagException.java new file mode 100644 index 00000000..6565a441 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/AuthorNagException.java @@ -0,0 +1,20 @@ +package org.bukkit.plugin; + +@SuppressWarnings("serial") +public class AuthorNagException extends RuntimeException { + private final String message; + + /** + * Constructs a new AuthorNagException based on the given Exception + * + * @param message Brief message explaining the cause of the exception + */ + public AuthorNagException(final String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java new file mode 100644 index 00000000..b45b6c1c --- /dev/null +++ b/src/main/java/org/bukkit/plugin/EventExecutor.java @@ -0,0 +1,84 @@ +package org.bukkit.plugin; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; + +// Paper start +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import com.destroystokyo.paper.event.executor.MethodHandleEventExecutor; +import com.destroystokyo.paper.event.executor.StaticMethodHandleEventExecutor; +import com.destroystokyo.paper.event.executor.asm.ASMEventExecutorGenerator; +import com.destroystokyo.paper.event.executor.asm.ClassDefiner; +import com.google.common.base.Preconditions; +// Paper end + +/** + * Interface which defines the class for event call backs to plugins + */ +public interface EventExecutor { + public void execute(Listener listener, Event event) throws EventException; + + // Paper start + ConcurrentMap> eventExecutorMap = new ConcurrentHashMap>() { + @Override + public Class computeIfAbsent(Method key, Function> mappingFunction) { + Class executorClass = get(key); + if (executorClass != null) + return executorClass; + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (key) { + executorClass = get(key); + if (executorClass != null) + return executorClass; + + return super.computeIfAbsent(key, mappingFunction); + } + } + }; + + public static EventExecutor create(Method m, Class eventClass) { + Preconditions.checkNotNull(m, "Null method"); + Preconditions.checkArgument(m.getParameterCount() != 0, "Incorrect number of arguments %s", m.getParameterCount()); + Preconditions.checkArgument(m.getParameterTypes()[0] == eventClass, "First parameter %s doesn't match event class %s", m.getParameterTypes()[0], eventClass); + ClassDefiner definer = ClassDefiner.getInstance(); + if (Modifier.isStatic(m.getModifiers())) { + return new StaticMethodHandleEventExecutor(eventClass, m); + } else if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) { + // get the existing generated EventExecutor class for the Method or generate one + Class executorClass = eventExecutorMap.computeIfAbsent(m, (__) -> { + String name = ASMEventExecutorGenerator.generateName(); + byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name); + return definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class); + }); + + try { + EventExecutor asmExecutor = executorClass.newInstance(); + // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception) + return new EventExecutor() { + @Override + public void execute(Listener listener, Event event) throws EventException { + if (!eventClass.isInstance(event)) return; + try { + asmExecutor.execute(listener, event); + } catch (Exception e) { + throw new EventException(e); + } + } + }; + } catch (InstantiationException | IllegalAccessException e) { + throw new AssertionError("Unable to initialize generated event executor", e); + } + } else { + return new MethodHandleEventExecutor(eventClass, m); + } + } + // Paper end +} diff --git a/src/main/java/org/bukkit/plugin/EventExecutor1.java b/src/main/java/org/bukkit/plugin/EventExecutor1.java new file mode 100644 index 00000000..ac5079a5 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/EventExecutor1.java @@ -0,0 +1,34 @@ +package org.bukkit.plugin; + +import java.lang.reflect.InvocationTargetException; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.event.Event; +import java.lang.reflect.Method; + +public class EventExecutor1 implements EventExecutor +{ + private Method method; + private Class eventClass; + + public EventExecutor1(Method method, Class eventClass) { + this.method = method; + this.eventClass = eventClass; + } + + @Override + public void execute(Listener listener, Event event) throws EventException { + try { + if (!this.eventClass.isAssignableFrom(event.getClass())) { + return; + } + this.method.invoke(listener, event); + } + catch (InvocationTargetException ex) { + throw new EventException(ex.getCause()); + } + catch (Throwable t) { + throw new EventException(t); + } + } +} diff --git a/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java b/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java new file mode 100644 index 00000000..b25447dc --- /dev/null +++ b/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java @@ -0,0 +1,25 @@ +package org.bukkit.plugin; + +/** + * Thrown when a plugin attempts to interact with the server when it is not + * enabled + */ +@SuppressWarnings("serial") +public class IllegalPluginAccessException extends RuntimeException { + + /** + * Creates a new instance of IllegalPluginAccessException + * without detail message. + */ + public IllegalPluginAccessException() {} + + /** + * Constructs an instance of IllegalPluginAccessException + * with the specified detail message. + * + * @param msg the detail message. + */ + public IllegalPluginAccessException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java b/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java new file mode 100644 index 00000000..0a77c2e2 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java @@ -0,0 +1,45 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid PluginDescriptionFile + */ +public class InvalidDescriptionException extends Exception { + private static final long serialVersionUID = 5721389122281775896L; + + /** + * Constructs a new InvalidDescriptionException based on the given + * Exception + * + * @param message Brief message explaining the cause of the exception + * @param cause Exception that triggered this Exception + */ + public InvalidDescriptionException(final Throwable cause, final String message) { + super(message, cause); + } + + /** + * Constructs a new InvalidDescriptionException based on the given + * Exception + * + * @param cause Exception that triggered this Exception + */ + public InvalidDescriptionException(final Throwable cause) { + super("Invalid plugin.yml", cause); + } + + /** + * Constructs a new InvalidDescriptionException with the given message + * + * @param message Brief message explaining the cause of the exception + */ + public InvalidDescriptionException(final String message) { + super(message); + } + + /** + * Constructs a new InvalidDescriptionException + */ + public InvalidDescriptionException() { + super("Invalid plugin.yml"); + } +} diff --git a/src/main/java/org/bukkit/plugin/InvalidPluginException.java b/src/main/java/org/bukkit/plugin/InvalidPluginException.java new file mode 100644 index 00000000..7ddf7b62 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/InvalidPluginException.java @@ -0,0 +1,49 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid Plugin file + */ +public class InvalidPluginException extends Exception { + private static final long serialVersionUID = -8242141640709409544L; + + /** + * Constructs a new InvalidPluginException based on the given Exception + * + * @param cause Exception that triggered this Exception + */ + public InvalidPluginException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new InvalidPluginException + */ + public InvalidPluginException() { + + } + + /** + * Constructs a new InvalidPluginException with the specified detail + * message and cause. + * + * @param message the detail message (which is saved for later retrieval + * by the getMessage() method). + * @param cause the cause (which is saved for later retrieval by the + * getCause() method). (A null value is permitted, and indicates that + * the cause is nonexistent or unknown.) + */ + public InvalidPluginException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new InvalidPluginException with the specified detail + * message + * + * @param message TThe detail message is saved for later retrieval by the + * getMessage() method. + */ + public InvalidPluginException(final String message) { + super(message); + } +} diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java new file mode 100644 index 00000000..55debf5d --- /dev/null +++ b/src/main/java/org/bukkit/plugin/Plugin.java @@ -0,0 +1,169 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.io.InputStream; +import java.util.logging.Logger; + +import org.bukkit.Server; +import org.bukkit.command.TabExecutor; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; + +/** + * Represents a Plugin + *

    + * The use of {@link PluginBase} is recommended for actual Implementation + */ +public interface Plugin extends TabExecutor { + /** + * Returns the folder that the plugin data's files are located in. The + * folder may not yet exist. + * + * @return The folder + */ + public File getDataFolder(); + + /** + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file + */ + public PluginDescriptionFile getDescription(); + + /** + * Gets a {@link FileConfiguration} for this plugin, read through + * "config.yml" + *

    + * If there is a default config.yml embedded in this plugin, it will be + * provided as a default for this Configuration. + * + * @return Plugin configuration + */ + public FileConfiguration getConfig(); + + /** + * Gets an embedded resource in this plugin + * + * @param filename Filename of the resource + * @return File if found, otherwise null + */ + public InputStream getResource(String filename); + + /** + * Saves the {@link FileConfiguration} retrievable by {@link #getConfig()}. + */ + public void saveConfig(); + + /** + * Saves the raw contents of the default config.yml file to the location + * retrievable by {@link #getConfig()}. + *

    + * This should fail silently if the config.yml already exists. + */ + public void saveDefaultConfig(); + + /** + * Saves the raw contents of any resource embedded with a plugin's .jar + * file assuming it can be found using {@link #getResource(String)}. + *

    + * The resource is saved into the plugin's data folder using the same + * hierarchy as the .jar file (subdirectories are preserved). + * + * @param resourcePath the embedded resource path to look for within the + * plugin's .jar file. (No preceding slash). + * @param replace if true, the embedded resource will overwrite the + * contents of an existing file. + * @throws IllegalArgumentException if the resource path is null, empty, + * or points to a nonexistent resource. + */ + public void saveResource(String resourcePath, boolean replace); + + /** + * Discards any data in {@link #getConfig()} and reloads from disk. + */ + public void reloadConfig(); + + /** + * Gets the associated PluginLoader responsible for this plugin + * + * @return PluginLoader that controls this plugin + */ + public PluginLoader getPluginLoader(); + + /** + * Returns the Server instance currently running this plugin + * + * @return Server running this plugin + */ + public Server getServer(); + + /** + * Returns a value indicating whether or not this plugin is currently + * enabled + * + * @return true if this plugin is enabled, otherwise false + */ + public boolean isEnabled(); + + /** + * Called when this plugin is disabled + */ + public void onDisable(); + + /** + * Called after a plugin is loaded but before it has been enabled. + *

    + * When multiple plugins are loaded, the onLoad() for all plugins is + * called before any onEnable() is called. + */ + public void onLoad(); + + /** + * Called when this plugin is enabled + */ + public void onEnable(); + + /** + * Simple boolean if we can still nag to the logs about things + * + * @return boolean whether we can nag + */ + public boolean isNaggable(); + + /** + * Set naggable state + * + * @param canNag is this plugin still naggable? + */ + public void setNaggable(boolean canNag); + + /** + * Gets a {@link ChunkGenerator} for use in a default world, as specified + * in the server configuration + * + * @param worldName Name of the world that this will be applied to + * @param id Unique ID, if any, that was specified to indicate which + * generator was requested + * @return ChunkGenerator for use in the default world generation + */ + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id); + + /** + * Returns the plugin logger associated with this server's logger. The + * returned logger automatically tags all log messages with the plugin's + * name. + * + * @return Logger associated with this plugin + */ + public Logger getLogger(); + + /** + * Returns the name of the plugin. + *

    + * This should return the bare name of the plugin and should be used for + * comparison. + * + * @return name of the plugin + */ + public String getName(); +} diff --git a/src/main/java/org/bukkit/plugin/PluginAwareness.java b/src/main/java/org/bukkit/plugin/PluginAwareness.java new file mode 100644 index 00000000..3f535ed5 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginAwareness.java @@ -0,0 +1,28 @@ +package org.bukkit.plugin; + +import java.util.Set; + +/** + * Represents a concept that a plugin is aware of. + *

    + * The internal representation may be singleton, or be a parameterized + * instance, but must be immutable. + */ +public interface PluginAwareness { + /** + * Each entry here represents a particular plugin's awareness. These can + * be checked by using {@link PluginDescriptionFile#getAwareness()}.{@link + * Set#contains(Object) contains(flag)}. + */ + public enum Flags implements PluginAwareness { + /** + * This specifies that all (text) resources stored in a plugin's jar + * use UTF-8 encoding. + * + * @deprecated all plugins are now assumed to be UTF-8 aware. + */ + @Deprecated + UTF8, + ; + } +} diff --git a/src/main/java/org/bukkit/plugin/PluginBase.java b/src/main/java/org/bukkit/plugin/PluginBase.java new file mode 100644 index 00000000..6031af11 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginBase.java @@ -0,0 +1,32 @@ +package org.bukkit.plugin; + +/** + * Represents a base {@link Plugin} + *

    + * Extend this class if your plugin is not a {@link + * org.bukkit.plugin.java.JavaPlugin} + */ +public abstract class PluginBase implements Plugin { + @Override + public final int hashCode() { + return getName().hashCode(); + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Plugin)) { + return false; + } + return getName().equals(((Plugin) obj).getName()); + } + + public final String getName() { + return getDescription().getName(); + } +} diff --git a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java new file mode 100644 index 00000000..7421bbec --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java @@ -0,0 +1,1084 @@ +package org.bukkit.plugin; + +import java.io.InputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.PluginCommand; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.AbstractConstruct; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.Tag; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * This type is the runtime-container for the information in the plugin.yml. + * All plugins must have a respective plugin.yml. For plugins written in java + * using the standard plugin loader, this file must be in the root of the jar + * file. + *

    + * When Bukkit loads a plugin, it needs to know some basic information about + * it. It reads this information from a YAML file, 'plugin.yml'. This file + * consists of a set of attributes, each defined on a new line and with no + * indentation. + *

    + * Every (almost* every) method corresponds with a specific entry in the + * plugin.yml. These are the required entries for every plugin.yml: + *

      + *
    • {@link #getName()} - name + *
    • {@link #getVersion()} - version + *
    • {@link #getMain()} - main + *
    + *

    + * Failing to include any of these items will throw an exception and cause the + * server to ignore your plugin. + *

    + * This is a list of the possible yaml keys, with specific details included in + * the respective method documentations: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    The description of the plugin.yml layout
    NodeMethodSummary
    name{@link #getName()}The unique name of plugin
    version{@link #getVersion()}A plugin revision identifier
    main{@link #getMain()}The plugin's initial class file
    author
    authors
    {@link #getAuthors()}The plugin contributors
    description{@link #getDescription()}Human readable plugin summary
    website{@link #getWebsite()}The URL to the plugin's site
    prefix{@link #getPrefix()}The token to prefix plugin log entries
    load{@link #getLoad()}The phase of server-startup this plugin will load during
    depend{@link #getDepend()}Other required plugins
    softdepend{@link #getSoftDepend()}Other plugins that add functionality
    loadbefore{@link #getLoadBefore()}The inverse softdepend
    commands{@link #getCommands()}The commands the plugin will register
    permissions{@link #getPermissions()}The permissions the plugin will register
    default-permission{@link #getPermissionDefault()}The default {@link Permission#getDefault() default} permission + * state for defined {@link #getPermissions() permissions} the plugin + * will register
    awareness{@link #getAwareness()}The concepts that the plugin acknowledges
    + *

    + * A plugin.yml example:

    + *name: Inferno
    + *version: 1.4.1
    + *description: This plugin is so 31337. You can set yourself on fire.
    + *# We could place every author in the authors list, but chose not to for illustrative purposes
    + *# Also, having an author distinguishes that person as the project lead, and ensures their
    + *# name is displayed first
    + *author: CaptainInflamo
    + *authors: [Cogito, verrier, EvilSeph]
    + *website: http://www.curse.com/server-mods/minecraft/myplugin
    + *
    + *main: com.captaininflamo.bukkit.inferno.Inferno
    + *depend: [NewFire, FlameWire]
    + *
    + *commands:
    + *  flagrate:
    + *    description: Set yourself on fire.
    + *    aliases: [combust_me, combustMe]
    + *    permission: inferno.flagrate
    + *    usage: Syntax error! Simply type /<command> to ignite yourself.
    + *  burningdeaths:
    + *    description: List how many times you have died by fire.
    + *    aliases: [burning_deaths, burningDeaths]
    + *    permission: inferno.burningdeaths
    + *    usage: |
    + *      /<command> [player]
    + *      Example: /<command> - see how many times you have burned to death
    + *      Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
    + *
    + *permissions:
    + *  inferno.*:
    + *    description: Gives access to all Inferno commands
    + *    children:
    + *      inferno.flagrate: true
    + *      inferno.burningdeaths: true
    + *      inferno.burningdeaths.others: true
    + *  inferno.flagrate:
    + *    description: Allows you to ignite yourself
    + *    default: true
    + *  inferno.burningdeaths:
    + *    description: Allows you to see how many times you have burned to death
    + *    default: true
    + *  inferno.burningdeaths.others:
    + *    description: Allows you to see how many times others have burned to death
    + *    default: op
    + *    children:
    + *      inferno.burningdeaths: true
    + *
    + */ +public final class PluginDescriptionFile { + private static final Pattern VALID_NAME = Pattern.compile("^[A-Za-z0-9 _.-]+$"); + private static final ThreadLocal YAML = new ThreadLocal() { + @Override + protected Yaml initialValue() { + return new Yaml(new SafeConstructor() { + { + yamlConstructors.put(null, new AbstractConstruct() { + @Override + public Object construct(final Node node) { + if (!node.getTag().startsWith("!@")) { + // Unknown tag - will fail + return SafeConstructor.undefinedConstructor.construct(node); + } + // Unknown awareness - provide a graceful substitution + return new PluginAwareness() { + @Override + public String toString() { + return node.toString(); + } + }; + } + }); + for (final PluginAwareness.Flags flag : PluginAwareness.Flags.values()) { + yamlConstructors.put(new Tag("!@" + flag.name()), new AbstractConstruct() { + @Override + public PluginAwareness.Flags construct(final Node node) { + return flag; + } + }); + } + } + }); + } + }; + String rawName = null; + private String name = null; + private String main = null; + private String classLoaderOf = null; + private List depend = ImmutableList.of(); + private List softDepend = ImmutableList.of(); + private List loadBefore = ImmutableList.of(); + private String version = null; + private Map> commands = null; + private String description = null; + private List authors = null; + private String website = null; + private String prefix = null; + private PluginLoadOrder order = PluginLoadOrder.POSTWORLD; + private List permissions = null; + private Map lazyPermissions = null; + private PermissionDefault defaultPerm = PermissionDefault.OP; + private Set awareness = ImmutableSet.of(); + + public PluginDescriptionFile(final InputStream stream) throws InvalidDescriptionException { + loadMap(asMap(YAML.get().load(stream))); + } + + /** + * Loads a PluginDescriptionFile from the specified reader + * + * @param reader The reader + * @throws InvalidDescriptionException If the PluginDescriptionFile is + * invalid + */ + public PluginDescriptionFile(final Reader reader) throws InvalidDescriptionException { + loadMap(asMap(YAML.get().load(reader))); + } + + /** + * Creates a new PluginDescriptionFile with the given detailed + * + * @param pluginName Name of this plugin + * @param pluginVersion Version of this plugin + * @param mainClass Full location of the main class of this plugin + */ + public PluginDescriptionFile(final String pluginName, final String pluginVersion, final String mainClass) { + name = rawName = pluginName; + if (!VALID_NAME.matcher(name).matches()) { + throw new IllegalArgumentException("name '" + name + "' contains invalid characters."); + } + name = name.replace(' ', '_'); + version = pluginVersion; + main = mainClass; + } + + /** + * Gives the name of the plugin. This name is a unique identifier for + * plugins. + *
      + *
    • Must consist of all alphanumeric characters, underscores, hyphon, + * and period (a-z,A-Z,0-9, _.-). Any other character will cause the + * plugin.yml to fail loading. + *
    • Used to determine the name of the plugin's data folder. Data + * folders are placed in the ./plugins/ directory by default, but this + * behavior should not be relied on. {@link Plugin#getDataFolder()} + * should be used to reference the data folder. + *
    • It is good practice to name your jar the same as this, for example + * 'MyPlugin.jar'. + *
    • Case sensitive. + *
    • The is the token referenced in {@link #getDepend()}, {@link + * #getSoftDepend()}, and {@link #getLoadBefore()}. + *
    • Using spaces in the plugin's name is deprecated. + *
    + *

    + * In the plugin.yml, this entry is named name. + *

    + * Example:

    name: MyPlugin
    + * + * @return the name of the plugin + */ + public String getName() { + return name; + } + + /** + * Gives the version of the plugin. + *
      + *
    • Version is an arbitrary string, however the most common format is + * MajorRelease.MinorRelease.Build (eg: 1.4.1). + *
    • Typically you will increment this every time you release a new + * feature or bug fix. + *
    • Displayed when a user types /version PluginName + *
    + *

    + * In the plugin.yml, this entry is named version. + *

    + * Example:

    version: 1.4.1
    + * + * @return the version of the plugin + */ + public String getVersion() { + return version; + } + + /** + * Gives the fully qualified name of the main class for a plugin. The + * format should follow the {@link ClassLoader#loadClass(String)} syntax + * to successfully be resolved at runtime. For most plugins, this is the + * class that extends {@link JavaPlugin}. + *
      + *
    • This must contain the full namespace including the class file + * itself. + *
    • If your namespace is org.bukkit.plugin, and your class + * file is called MyPlugin then this must be + * org.bukkit.plugin.MyPlugin + *
    • No plugin can use org.bukkit. as a base package for + * any class, including the main class. + *
    + *

    + * In the plugin.yml, this entry is named main. + *

    + * Example: + *

    main: org.bukkit.plugin.MyPlugin
    + * + * @return the fully qualified main class for the plugin + */ + public String getMain() { + return main; + } + + /** + * Gives a human-friendly description of the functionality the plugin + * provides. + *
      + *
    • The description can have multiple lines. + *
    • Displayed when a user types /version PluginName + *
    + *

    + * In the plugin.yml, this entry is named description. + *

    + * Example: + *

    description: This plugin is so 31337. You can set yourself on fire.
    + * + * @return description of this plugin, or null if not specified + */ + public String getDescription() { + return description; + } + + /** + * Gives the phase of server startup that the plugin should be loaded. + *
      + *
    • Possible values are in {@link PluginLoadOrder}. + *
    • Defaults to {@link PluginLoadOrder#POSTWORLD}. + *
    • Certain caveats apply to each phase. + *
    • When different, {@link #getDepend()}, {@link #getSoftDepend()}, and + * {@link #getLoadBefore()} become relative in order loaded per-phase. + * If a plugin loads at STARTUP, but a dependency loads + * at POSTWORLD, the dependency will not be loaded before + * the plugin is loaded. + *
    + *

    + * In the plugin.yml, this entry is named load. + *

    + * Example:

    load: STARTUP
    + * + * @return the phase when the plugin should be loaded + */ + public PluginLoadOrder getLoad() { + return order; + } + + /** + * Gives the list of authors for the plugin. + *
      + *
    • Gives credit to the developer. + *
    • Used in some server error messages to provide helpful feedback on + * who to contact when an error occurs. + *
    • A bukkit.org forum handle or email address is recommended. + *
    • Is displayed when a user types /version PluginName + *
    • authors must be in YAML list + * format. + *
    + *

    + * In the plugin.yml, this has two entries, author and + * authors. + *

    + * Single author example: + *

    author: CaptainInflamo
    + * Multiple author example: + *
    authors: [Cogito, verrier, EvilSeph]
    + * When both are specified, author will be the first entry in the list, so + * this example: + *
    author: Grum
    +     *authors:
    +     *- feildmaster
    +     *- amaranth
    + * Is equivilant to this example: + *
    authors: [Grum, feildmaster, aramanth]
    + * + * @return an immutable list of the plugin's authors + */ + public List getAuthors() { + return authors; + } + + /** + * Gives the plugin's or plugin's author's website. + *
      + *
    • A link to the Curse page that includes documentation and downloads + * is highly recommended. + *
    • Displayed when a user types /version PluginName + *
    + *

    + * In the plugin.yml, this entry is named website. + *

    + * Example: + *

    website: http://www.curse.com/server-mods/minecraft/myplugin
    + * + * @return description of this plugin, or null if not specified + */ + public String getWebsite() { + return website; + } + + /** + * Gives a list of other plugins that the plugin requires. + *
      + *
    • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
    • If any plugin listed here is not found, your plugin will fail to + * load at startup. + *
    • If multiple plugins list each other in depend, + * creating a network with no individual plugin does not list another + * plugin in the network, + * all plugins in that network will fail. + *
    • depend must be in must be in YAML list + * format. + *
    + *

    + * In the plugin.yml, this entry is named depend. + *

    + * Example: + *

    depend:
    +     *- OnePlugin
    +     *- AnotherPlugin
    + * + * @return immutable list of the plugin's dependencies + */ + public List getDepend() { + return depend; + } + + /** + * Gives a list of other plugins that the plugin requires for full + * functionality. The {@link PluginManager} will make best effort to treat + * all entries here as if they were a {@link #getDepend() dependency}, but + * will never fail because of one of these entries. + *
      + *
    • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
    • When an unresolvable plugin is listed, it will be ignored and does + * not affect load order. + *
    • When a circular dependency occurs (a network of plugins depending + * or soft-dependending each other), it will arbitrarily choose a + * plugin that can be resolved when ignoring soft-dependencies. + *
    • softdepend must be in YAML list + * format. + *
    + *

    + * In the plugin.yml, this entry is named softdepend. + *

    + * Example: + *

    softdepend: [OnePlugin, AnotherPlugin]
    + * + * @return immutable list of the plugin's preferred dependencies + */ + public List getSoftDepend() { + return softDepend; + } + + /** + * Gets the list of plugins that should consider this plugin a + * soft-dependency. + *
      + *
    • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
    • The plugin should load before any other plugins listed here. + *
    • Specifying another plugin here is strictly equivalent to having the + * specified plugin's {@link #getSoftDepend()} include {@link + * #getName() this plugin}. + *
    • loadbefore must be in YAML list + * format. + *
    + *

    + * In the plugin.yml, this entry is named loadbefore. + *

    + * Example: + *

    loadbefore:
    +     *- OnePlugin
    +     *- AnotherPlugin
    + * + * @return immutable list of plugins that should consider this plugin a + * soft-dependency + */ + public List getLoadBefore() { + return loadBefore; + } + + /** + * Gives the token to prefix plugin-specific logging messages with. + *
      + *
    • This includes all messages using {@link Plugin#getLogger()}. + *
    • If not specified, the server uses the plugin's {@link #getName() + * name}. + *
    • This should clearly indicate what plugin is being logged. + *
    + *

    + * In the plugin.yml, this entry is named prefix. + *

    + * Example:

    prefix: ex-why-zee
    + * + * @return the prefixed logging token, or null if not specified + */ + public String getPrefix() { + return prefix; + } + + /** + * Gives the map of command-name to command-properties. Each entry in this + * map corresponds to a single command and the respective values are the + * properties of the command. Each property, with the exception of + * aliases, can be defined at runtime using methods in {@link + * PluginCommand} and are defined here only as a convenience. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    The command section's description
    NodeMethodTypeDescriptionExample
    description{@link PluginCommand#setDescription(String)}StringA user-friendly description for a command. It is useful for + * documentation purposes as well as in-game help.
    description: Set yourself on fire
    aliases{@link PluginCommand#setAliases(List)}String or List of + * stringsAlternative command names, with special usefulness for commands + * that are already registered. Aliases are not effective when + * defined at runtime, so the plugin description file is the + * only way to have them properly defined. + *

    + * Note: Command aliases may not have a colon in them.

    Single alias format: + *
    aliases: combust_me
    or + * multiple alias format: + *
    aliases: [combust_me, combustMe]
    permission{@link PluginCommand#setPermission(String)}StringThe name of the {@link Permission} required to use the command. + * A user without the permission will receive the specified + * message (see {@linkplain + * PluginCommand#setPermissionMessage(String) below}), or a + * standard one if no specific message is defined. Without the + * permission node, no {@link + * PluginCommand#setExecutor(CommandExecutor) CommandExecutor} or + * {@link PluginCommand#setTabCompleter(TabCompleter) + * TabCompleter} will be called.
    permission: inferno.flagrate
    permission-message{@link PluginCommand#setPermissionMessage(String)}String
      + *
    • Displayed to a player that attempts to use a command, but + * does not have the required permission. See {@link + * PluginCommand#getPermission() above}. + *
    • <permission> is a macro that is replaced with the + * permission node required to use the command. + *
    • Using empty quotes is a valid way to indicate nothing + * should be displayed to a player. + *
    permission-message: You do not have /<permission>
    usage{@link PluginCommand#setUsage(String)}StringThis message is displayed to a player when the {@link + * PluginCommand#setExecutor(CommandExecutor)} {@linkplain + * CommandExecutor#onCommand(CommandSender,Command,String,String[]) + * returns false}. <command> is a macro that is replaced + * the command issued.
    usage: Syntax error! Perhaps you meant /<command> PlayerName?
    + * It is worth noting that to use a colon in a yaml, like + * `usage: Usage: /god [player]', you need to + * surround + * the message with double-quote: + *
    usage: "Usage: /god [player]"
    + * The commands are structured as a hiearchy of nested mappings. + * The primary (top-level, no intendentation) node is + * `commands', while each individual command name is + * indented, indicating it maps to some value (in our case, the + * properties of the table above). + *

    + * Here is an example bringing together the piecemeal examples above, as + * well as few more definitions:

    +     *commands:
    +     *  flagrate:
    +     *    description: Set yourself on fire.
    +     *    aliases: [combust_me, combustMe]
    +     *    permission: inferno.flagrate
    +     *    permission-message: You do not have /<permission>
    +     *    usage: Syntax error! Perhaps you meant /<command> PlayerName?
    +     *  burningdeaths:
    +     *    description: List how many times you have died by fire.
    +     *    aliases:
    +     *    - burning_deaths
    +     *    - burningDeaths
    +     *    permission: inferno.burningdeaths
    +     *    usage: |
    +     *      /<command> [player]
    +     *      Example: /<command> - see how many times you have burned to death
    +     *      Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
    +     *  # The next command has no description, aliases, etc. defined, but is still valid
    +     *  # Having an empty declaration is useful for defining the description, permission, and messages from a configuration dynamically
    +     *  apocalypse:
    +     *
    + * Note: Command names may not have a colon in their name. + * + * @return the commands this plugin will register + */ + public Map> getCommands() { + return commands; + } + + /** + * Gives the list of permissions the plugin will register at runtime, + * immediately proceding enabling. The format for defining permissions is + * a map from permission name to properties. To represent a map without + * any specific property, empty curly-braces ( + * {} ) may be used (as a null value is not + * accepted, unlike the {@link #getCommands() commands} above). + *

    + * A list of optional properties for permissions: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    The permission section's description
    NodeDescriptionExample
    descriptionPlaintext (user-friendly) description of what the permission + * is for.
    description: Allows you to set yourself on fire
    defaultThe default state for the permission, as defined by {@link + * Permission#getDefault()}. If not defined, it will be set to + * the value of {@link PluginDescriptionFile#getPermissionDefault()}. + *

    + * For reference:

      + *
    • true - Represents a positive assignment to + * {@link Permissible permissibles}. + *
    • false - Represents no assignment to {@link + * Permissible permissibles}. + *
    • op - Represents a positive assignment to + * {@link Permissible#isOp() operator permissibles}. + *
    • notop - Represents a positive assignment to + * {@link Permissible#isOp() non-operator permissibiles}. + *
    default: true
    childrenAllows other permissions to be set as a {@linkplain + * Permission#getChildren() relation} to the parent permission. + * When a parent permissions is assigned, child permissions are + * respectively assigned as well. + *
      + *
    • When a parent permission is assigned negatively, child + * permissions are assigned based on an inversion of their + * association. + *
    • When a parent permission is assigned positively, child + * permissions are assigned based on their association. + *
    + *

    + * Child permissions may be defined in a number of ways:

      + *
    • Children may be defined as a list of + * names. Using a list will treat all children associated + * positively to their parent. + *
    • Children may be defined as a map. Each permission name maps + * to either a boolean (representing the association), or a + * nested permission definition (just as another permission). + * Using a nested definition treats the child as a positive + * association. + *
    • A nested permission definition must be a map of these same + * properties. To define a valid nested permission without + * defining any specific property, empty curly-braces ( + * {} ) must be used. + *
    • A nested permission may carry it's own nested permissions + * as children, as they may also have nested permissions, and + * so forth. There is no direct limit to how deep the + * permission tree is defined. + *
    As a list: + *
    children: [inferno.flagrate, inferno.burningdeaths]
    + * Or as a mapping: + *
    children:
    +     *  inferno.flagrate: true
    +     *  inferno.burningdeaths: true
    + * An additional example showing basic nested values can be seen + * here. + *
    + * The permissions are structured as a hiearchy of nested mappings. + * The primary (top-level, no intendentation) node is + * `permissions', while each individual permission name is + * indented, indicating it maps to some value (in our case, the + * properties of the table above). + *

    + * Here is an example using some of the properties:

    +     *permissions:
    +     *  inferno.*:
    +     *    description: Gives access to all Inferno commands
    +     *    children:
    +     *      inferno.flagrate: true
    +     *      inferno.burningdeaths: true
    +     *  inferno.flagate:
    +     *    description: Allows you to ignite yourself
    +     *    default: true
    +     *  inferno.burningdeaths:
    +     *    description: Allows you to see how many times you have burned to death
    +     *    default: true
    +     *
    + * Another example, with nested definitions, can be found here. + * + * @return the permissions this plugin will register + */ + public List getPermissions() { + if (permissions == null) { + if (lazyPermissions == null) { + permissions = ImmutableList.of(); + } else { + permissions = ImmutableList.copyOf(Permission.loadPermissions(lazyPermissions, "Permission node '%s' in plugin description file for " + getFullName() + " is invalid", defaultPerm)); + lazyPermissions = null; + } + } + return permissions; + } + + /** + * Gives the default {@link Permission#getDefault() default} state of + * {@link #getPermissions() permissions} registered for the plugin. + *
      + *
    • If not specified, it will be {@link PermissionDefault#OP}. + *
    • It is matched using {@link PermissionDefault#getByName(String)} + *
    • It only affects permissions that do not define the + * default node. + *
    • It may be any value in {@link PermissionDefault}. + *
    + *

    + * In the plugin.yml, this entry is named default-permission. + *

    + * Example:

    default-permission: NOT_OP
    + * + * @return the default value for the plugin's permissions + */ + public PermissionDefault getPermissionDefault() { + return defaultPerm; + } + + /** + * Gives a set of every {@link PluginAwareness} for a plugin. An awareness + * dictates something that a plugin developer acknowledges when the plugin + * is compiled. Some implementions may define extra awarenesses that are + * not included in the API. Any unrecognized + * awareness (one unsupported or in a future version) will cause a dummy + * object to be created instead of failing. + * + *
      + *
    • Currently only supports the enumerated values in {@link + * PluginAwareness.Flags}. + *
    • Each awareness starts the identifier with bang-at + * (!@). + *
    • Unrecognized (future / unimplemented) entries are quietly replaced + * by a generic object that implements PluginAwareness. + *
    • A type of awareness must be defined by the runtime and acknowledged + * by the API, effectively discluding any derived type from any + * plugin's classpath. + *
    • awareness must be in YAML list + * format. + *
    + *

    + * In the plugin.yml, this entry is named awareness. + *

    + * Example:

    awareness:
    +     *- !@UTF8
    + *

    + * Note: Although unknown versions of some future awareness are + * gracefully substituted, previous versions of Bukkit (ones prior to the + * first implementation of awareness) will fail to load a plugin that + * defines any awareness. + * + * @return a set containing every awareness for the plugin + */ + public Set getAwareness() { + return awareness; + } + + /** + * Returns the name of a plugin, including the version. This method is + * provided for convenience; it uses the {@link #getName()} and {@link + * #getVersion()} entries. + * + * @return a descriptive name of the plugin and respective version + */ + public String getFullName() { + return name + " v" + version; + } + + /** + * @return unused + * @deprecated unused + */ + @Deprecated + public String getClassLoaderOf() { + return classLoaderOf; + } + + /** + * Saves this PluginDescriptionFile to the given writer + * + * @param writer Writer to output this file to + */ + public void save(Writer writer) { + YAML.get().dump(saveMap(), writer); + } + + private void loadMap(Map map) throws InvalidDescriptionException { + try { + name = rawName = map.get("name").toString(); + + if (!VALID_NAME.matcher(name).matches()) { + throw new InvalidDescriptionException("name '" + name + "' contains invalid characters."); + } + name = name.replace(' ', '_'); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "name is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "name is of wrong type"); + } + + try { + version = map.get("version").toString(); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "version is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "version is of wrong type"); + } + + try { + main = map.get("main").toString(); + if (main.startsWith("org.bukkit.")) { + throw new InvalidDescriptionException("main may not be within the org.bukkit namespace"); + } + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "main is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "main is of wrong type"); + } + + if (map.get("commands") != null) { + ImmutableMap.Builder> commandsBuilder = ImmutableMap.>builder(); + try { + for (Map.Entry command : ((Map) map.get("commands")).entrySet()) { + ImmutableMap.Builder commandBuilder = ImmutableMap.builder(); + if (command.getValue() != null) { + for (Map.Entry commandEntry : ((Map) command.getValue()).entrySet()) { + if (commandEntry.getValue() instanceof Iterable) { + // This prevents internal alias list changes + ImmutableList.Builder commandSubList = ImmutableList.builder(); + for (Object commandSubListItem : (Iterable) commandEntry.getValue()) { + if (commandSubListItem != null) { + commandSubList.add(commandSubListItem); + } + } + commandBuilder.put(commandEntry.getKey().toString(), commandSubList.build()); + } else if (commandEntry.getValue() != null) { + commandBuilder.put(commandEntry.getKey().toString(), commandEntry.getValue()); + } + } + } + commandsBuilder.put(command.getKey().toString(), commandBuilder.build()); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "commands are of wrong type"); + } + commands = commandsBuilder.build(); + } + + if (map.get("class-loader-of") != null) { + classLoaderOf = map.get("class-loader-of").toString(); + } + + depend = makePluginNameList(map, "depend"); + softDepend = makePluginNameList(map, "softdepend"); + loadBefore = makePluginNameList(map, "loadbefore"); + + if (map.get("website") != null) { + website = map.get("website").toString(); + } + + if (map.get("description") != null) { + description = map.get("description").toString(); + } + + if (map.get("load") != null) { + try { + order = PluginLoadOrder.valueOf(((String) map.get("load")).toUpperCase(java.util.Locale.ENGLISH).replaceAll("\\W", "")); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "load is of wrong type"); + } catch (IllegalArgumentException ex) { + throw new InvalidDescriptionException(ex, "load is not a valid choice"); + } + } + + if (map.get("authors") != null) { + ImmutableList.Builder authorsBuilder = ImmutableList.builder(); + if (map.get("author") != null) { + authorsBuilder.add(map.get("author").toString()); + } + try { + for (Object o : (Iterable) map.get("authors")) { + authorsBuilder.add(o.toString()); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "authors are of wrong type"); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "authors are improperly defined"); + } + authors = authorsBuilder.build(); + } else if (map.get("author") != null) { + authors = ImmutableList.of(map.get("author").toString()); + } else { + authors = ImmutableList.of(); + } + + if (map.get("default-permission") != null) { + try { + defaultPerm = PermissionDefault.getByName(map.get("default-permission").toString()); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "default-permission is of wrong type"); + } catch (IllegalArgumentException ex) { + throw new InvalidDescriptionException(ex, "default-permission is not a valid choice"); + } + } + + if (map.get("awareness") instanceof Iterable) { + Set awareness = new HashSet(); + try { + for (Object o : (Iterable) map.get("awareness")) { + awareness.add((PluginAwareness) o); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "awareness has wrong type"); + } + this.awareness = ImmutableSet.copyOf(awareness); + } + + try { + lazyPermissions = (Map) map.get("permissions"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "permissions are of the wrong type"); + } + + if (map.get("prefix") != null) { + prefix = map.get("prefix").toString(); + } + } + + private static List makePluginNameList(final Map map, final String key) throws InvalidDescriptionException { + final Object value = map.get(key); + if (value == null) { + return ImmutableList.of(); + } + + final ImmutableList.Builder builder = ImmutableList.builder(); + try { + for (final Object entry : (Iterable) value) { + builder.add(entry.toString().replace(' ', '_')); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, key + " is of wrong type"); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "invalid " + key + " format"); + } + return builder.build(); + } + + private Map saveMap() { + Map map = new HashMap(); + + map.put("name", name); + map.put("main", main); + map.put("version", version); + map.put("order", order.toString()); + map.put("default-permission", defaultPerm.toString()); + + if (commands != null) { + map.put("command", commands); + } + if (depend != null) { + map.put("depend", depend); + } + if (softDepend != null) { + map.put("softdepend", softDepend); + } + if (website != null) { + map.put("website", website); + } + if (description != null) { + map.put("description", description); + } + + if (authors.size() == 1) { + map.put("author", authors.get(0)); + } else if (authors.size() > 1) { + map.put("authors", authors); + } + + if (classLoaderOf != null) { + map.put("class-loader-of", classLoaderOf); + } + + if (prefix != null) { + map.put("prefix", prefix); + } + + return map; + } + + private Map asMap(Object object) throws InvalidDescriptionException { + if (object instanceof Map) { + return (Map) object; + } + throw new InvalidDescriptionException(object + " is not properly structured."); + } + + /** + * @return internal use + * @deprecated Internal use + */ + @Deprecated + public String getRawName() { + return rawName; + } +} diff --git a/src/main/java/org/bukkit/plugin/PluginLoadOrder.java b/src/main/java/org/bukkit/plugin/PluginLoadOrder.java new file mode 100644 index 00000000..b77436fd --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginLoadOrder.java @@ -0,0 +1,17 @@ +package org.bukkit.plugin; + +/** + * Represents the order in which a plugin should be initialized and enabled + */ +public enum PluginLoadOrder { + + /** + * Indicates that the plugin will be loaded at startup + */ + STARTUP, + /** + * Indicates that the plugin will be loaded after the first/default world + * was created + */ + POSTWORLD +} diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java new file mode 100644 index 00000000..8fa8166c --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginLoader.java @@ -0,0 +1,91 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.bukkit.event.Event; +import org.bukkit.event.Listener; + +/** + * Represents a plugin loader, which handles direct access to specific types + * of plugins + */ +public interface PluginLoader { + + /** + * Loads the plugin contained in the specified file + * + * @param file File to attempt to load + * @return Plugin that was contained in the specified file, or null if + * unsuccessful + * @throws InvalidPluginException Thrown when the specified file is not a + * plugin + * @throws UnknownDependencyException If a required dependency could not + * be found + */ + public Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException; + + /** + * Loads a PluginDescriptionFile from the specified file + * + * @param file File to attempt to load from + * @return A new PluginDescriptionFile loaded from the plugin.yml in the + * specified file + * @throws InvalidDescriptionException If the plugin description file + * could not be created + */ + public PluginDescriptionFile getPluginDescription(File file) throws InvalidDescriptionException; + + /** + * Returns a list of all filename filters expected by this PluginLoader + * + * @return The filters + */ + public Pattern[] getPluginFileFilters(); + + /** + * Creates and returns registered listeners for the event classes used in + * this listener + * + * @param listener The object that will handle the eventual call back + * @param plugin The plugin to use when creating registered listeners + * @return The registered listeners. + */ + public Map, Set> createRegisteredListeners(Listener listener, Plugin plugin); + + /** + * Enables the specified plugin + *

    + * Attempting to enable a plugin that is already enabled will have no + * effect + * + * @param plugin Plugin to enable + */ + public void enablePlugin(Plugin plugin); + + /** + * Disables the specified plugin + *

    + * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + */ + void disablePlugin(Plugin plugin); + + // Paper start - close Classloader on disable + /** + * Disables the specified plugin + *

    + * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + * @param closeClassloader if the classloader for the Plugin should be closed + */ + // provide default to allow other PluginLoader implementations to work + default void disablePlugin(Plugin plugin, boolean closeClassloader) { + disablePlugin(plugin); + } + // Paper end - close Classloader on disable +} diff --git a/src/main/java/org/bukkit/plugin/PluginLogger.java b/src/main/java/org/bukkit/plugin/PluginLogger.java new file mode 100644 index 00000000..f43c10b0 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginLogger.java @@ -0,0 +1,36 @@ +package org.bukkit.plugin; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * The PluginLogger class is a modified {@link Logger} that prepends all + * logging calls with the name of the plugin doing the logging. The API for + * PluginLogger is exactly the same as {@link Logger}. + * + * @see Logger + */ +public class PluginLogger extends Logger { + private String pluginName; + + /** + * Creates a new PluginLogger that extracts the name from a plugin. + * + * @param context A reference to the plugin + */ + public PluginLogger(Plugin context) { + super(context.getClass().getCanonicalName(), null); + String prefix = context.getDescription().getPrefix(); + pluginName = prefix != null ? new StringBuilder().append("[").append(prefix).append("] ").toString() : "[" + context.getDescription().getName() + "] "; + setParent(context.getServer().getLogger()); + setLevel(Level.ALL); + } + + @Override + public void log(LogRecord logRecord) { + logRecord.setMessage(pluginName + logRecord.getMessage()); + super.log(logRecord); + } + +} diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java new file mode 100644 index 00000000..da6976eb --- /dev/null +++ b/src/main/java/org/bukkit/plugin/PluginManager.java @@ -0,0 +1,306 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.util.Set; + +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; + +/** + * Handles all plugin management from the Server + */ +public interface PluginManager { + + /** + * Registers the specified plugin loader + * + * @param loader Class name of the PluginLoader to register + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ + public void registerInterface(Class loader) throws IllegalArgumentException; + + /** + * Checks if the given plugin is loaded and returns it when applicable + *

    + * Please note that the name of the plugin is case-sensitive + * + * @param name Name of the plugin to check + * @return Plugin if it exists, otherwise null + */ + public Plugin getPlugin(String name); + + /** + * Gets a list of all currently loaded plugins + * + * @return Array of Plugins + */ + public Plugin[] getPlugins(); + + /** + * Checks if the given plugin is enabled or not + *

    + * Please note that the name of the plugin is case-sensitive. + * + * @param name Name of the plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(String name); + + /** + * Checks if the given plugin is enabled or not + * + * @param plugin Plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(Plugin plugin); + + /** + * Loads the plugin in the specified file + *

    + * File must be valid according to the current enabled Plugin interfaces + * + * @param file File containing the plugin to load + * @return The Plugin loaded, or null if it was invalid + * @throws InvalidPluginException Thrown when the specified file is not a + * valid plugin + * @throws InvalidDescriptionException Thrown when the specified file + * contains an invalid description + * @throws UnknownDependencyException If a required dependency could not + * be resolved + */ + public Plugin loadPlugin(File file) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException; + + /** + * Loads the plugins contained within the specified directory + * + * @param directory Directory to check for plugins + * @return A list of all plugins loaded + */ + public Plugin[] loadPlugins(File directory); + + /** + * Disables all the loaded plugins + */ + void disablePlugins(); + + // Paper start - close Classloader on disable + /** + * Disables the specified plugin + *

    + * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + * @param closeClassloader if the classloader for the Plugin should be closed + */ + void disablePlugin(Plugin plugin, boolean closeClassloader); + // Paper end - close Classloader on disable + + /** + * Disables and removes all plugins + */ + public void clearPlugins(); + + /** + * Calls an event with the given details + * + * @param event Event details + * @throws IllegalStateException Thrown when an asynchronous event is + * fired from synchronous code. + *

    + * Note: This is best-effort basis, and should not be used to test + * synchronized state. This is an indicator for flawed flow logic. + */ + public void callEvent(Event event) throws IllegalStateException; + + /** + * Registers all the events in the given listener class + * + * @param listener Listener to register + * @param plugin Plugin to register + */ + public void registerEvents(Listener listener, Plugin plugin); + + /** + * Registers the specified executor to the given event class + * + * @param event Event type to register + * @param listener Listener to register + * @param priority Priority to register this event at + * @param executor EventExecutor to register + * @param plugin Plugin to register + */ + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin); + + /** + * Registers the specified executor to the given event class + * + * @param event Event type to register + * @param listener Listener to register + * @param priority Priority to register this event at + * @param executor EventExecutor to register + * @param plugin Plugin to register + * @param ignoreCancelled Whether to pass cancelled events or not + */ + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled); + + /** + * Enables the specified plugin + *

    + * Attempting to enable a plugin that is already enabled will have no + * effect + * + * @param plugin Plugin to enable + */ + public void enablePlugin(Plugin plugin); + + /** + * Disables the specified plugin + *

    + * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + */ + public void disablePlugin(Plugin plugin); + + /** + * Gets a {@link Permission} from its fully qualified name + * + * @param name Name of the permission + * @return Permission, or null if none + */ + public Permission getPermission(String name); + + /** + * Adds a {@link Permission} to this plugin manager. + *

    + * If a permission is already defined with the given name of the new + * permission, an exception will be thrown. + * + * @param perm Permission to add + * @throws IllegalArgumentException Thrown when a permission with the same + * name already exists + */ + public void addPermission(Permission perm); + + /** + * Removes a {@link Permission} registration from this plugin manager. + *

    + * If the specified permission does not exist in this plugin manager, + * nothing will happen. + *

    + * Removing a permission registration will not remove the + * permission from any {@link Permissible}s that have it. + * + * @param perm Permission to remove + */ + public void removePermission(Permission perm); + + /** + * Removes a {@link Permission} registration from this plugin manager. + *

    + * If the specified permission does not exist in this plugin manager, + * nothing will happen. + *

    + * Removing a permission registration will not remove the + * permission from any {@link Permissible}s that have it. + * + * @param name Permission to remove + */ + public void removePermission(String name); + + /** + * Gets the default permissions for the given op status + * + * @param op Which set of default permissions to get + * @return The default permissions + */ + public Set getDefaultPermissions(boolean op); + + /** + * Recalculates the defaults for the given {@link Permission}. + *

    + * This will have no effect if the specified permission is not registered + * here. + * + * @param perm Permission to recalculate + */ + public void recalculatePermissionDefaults(Permission perm); + + /** + * Subscribes the given Permissible for information about the requested + * Permission, by name. + *

    + * If the specified Permission changes in any form, the Permissible will + * be asked to recalculate. + * + * @param permission Permission to subscribe to + * @param permissible Permissible subscribing + */ + public void subscribeToPermission(String permission, Permissible permissible); + + /** + * Unsubscribes the given Permissible for information about the requested + * Permission, by name. + * + * @param permission Permission to unsubscribe from + * @param permissible Permissible subscribing + */ + public void unsubscribeFromPermission(String permission, Permissible permissible); + + /** + * Gets a set containing all subscribed {@link Permissible}s to the given + * permission, by name + * + * @param permission Permission to query for + * @return Set containing all subscribed permissions + */ + public Set getPermissionSubscriptions(String permission); + + /** + * Subscribes to the given Default permissions by operator status + *

    + * If the specified defaults change in any form, the Permissible will be + * asked to recalculate. + * + * @param op Default list to subscribe to + * @param permissible Permissible subscribing + */ + public void subscribeToDefaultPerms(boolean op, Permissible permissible); + + /** + * Unsubscribes from the given Default permissions by operator status + * + * @param op Default list to unsubscribe from + * @param permissible Permissible subscribing + */ + public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible); + + /** + * Gets a set containing all subscribed {@link Permissible}s to the given + * default list, by op status + * + * @param op Default list to query for + * @return Set containing all subscribed permissions + */ + public Set getDefaultPermSubscriptions(boolean op); + + /** + * Gets a set of all registered permissions. + *

    + * This set is a copy and will not be modified live. + * + * @return Set containing all current registered permissions + */ + public Set getPermissions(); + + /** + * Returns whether or not timing code should be used for event calls + * + * @return True if event timings are to be used + */ + public boolean useTimings(); +} diff --git a/src/main/java/org/bukkit/plugin/RegisteredListener.java b/src/main/java/org/bukkit/plugin/RegisteredListener.java new file mode 100644 index 00000000..944f9088 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/RegisteredListener.java @@ -0,0 +1,73 @@ +package org.bukkit.plugin; + +import org.bukkit.event.*; + +/** + * Stores relevant information for plugin listeners + */ +public class RegisteredListener { + private final Listener listener; + private final EventPriority priority; + private final Plugin plugin; + private final EventExecutor executor; + private final boolean ignoreCancelled; + + public RegisteredListener(final Listener listener, final EventExecutor executor, final EventPriority priority, final Plugin plugin, final boolean ignoreCancelled) { + this.listener = listener; + this.priority = priority; + this.plugin = plugin; + this.executor = executor; + this.ignoreCancelled = ignoreCancelled; + } + + /** + * Gets the listener for this registration + * + * @return Registered Listener + */ + public Listener getListener() { + return listener; + } + + /** + * Gets the plugin for this registration + * + * @return Registered Plugin + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Gets the priority for this registration + * + * @return Registered Priority + */ + public EventPriority getPriority() { + return priority; + } + + /** + * Calls the event executor + * + * @param event The event + * @throws EventException If an event handler throws an exception. + */ + public void callEvent(final Event event) throws EventException { + if (event instanceof Cancellable) { + if (((Cancellable) event).isCancelled() && isIgnoringCancelled()) { + return; + } + } + executor.execute(listener, event); + } + + /** + * Whether this listener accepts cancelled events + * + * @return True when ignoring cancelled events + */ + public boolean isIgnoringCancelled() { + return ignoreCancelled; + } +} diff --git a/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java b/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java new file mode 100644 index 00000000..ba3ff15e --- /dev/null +++ b/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java @@ -0,0 +1,46 @@ +package org.bukkit.plugin; + +/** + * A registered service provider. + * + * @param Service + */ +public class RegisteredServiceProvider implements Comparable> { + + private Class service; + private Plugin plugin; + private T provider; + private ServicePriority priority; + + public RegisteredServiceProvider(Class service, T provider, ServicePriority priority, Plugin plugin) { + + this.service = service; + this.plugin = plugin; + this.provider = provider; + this.priority = priority; + } + + public Class getService() { + return service; + } + + public Plugin getPlugin() { + return plugin; + } + + public T getProvider() { + return provider; + } + + public ServicePriority getPriority() { + return priority; + } + + public int compareTo(RegisteredServiceProvider other) { + if (priority.ordinal() == other.getPriority().ordinal()) { + return 0; + } else { + return priority.ordinal() < other.getPriority().ordinal() ? 1 : -1; + } + } +} diff --git a/src/main/java/org/bukkit/plugin/ServicePriority.java b/src/main/java/org/bukkit/plugin/ServicePriority.java new file mode 100644 index 00000000..4afe0fb3 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/ServicePriority.java @@ -0,0 +1,12 @@ +package org.bukkit.plugin; + +/** + * Represents various priorities of a provider. + */ +public enum ServicePriority { + Lowest, + Low, + Normal, + High, + Highest +} diff --git a/src/main/java/org/bukkit/plugin/ServicesManager.java b/src/main/java/org/bukkit/plugin/ServicesManager.java new file mode 100644 index 00000000..5d45ffb2 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/ServicesManager.java @@ -0,0 +1,106 @@ +package org.bukkit.plugin; + +import java.util.Collection; +import java.util.List; + +/** + * Manages services and service providers. Services are an interface + * specifying a list of methods that a provider must implement. Providers are + * implementations of these services. A provider can be queried from the + * services manager in order to use a service (if one is available). If + * multiple plugins register a service, then the service with the highest + * priority takes precedence. + */ +public interface ServicesManager { + + /** + * Register a provider of a service. + * + * @param Provider + * @param service service class + * @param provider provider to register + * @param plugin plugin with the provider + * @param priority priority of the provider + */ + public void register(Class service, T provider, Plugin plugin, ServicePriority priority); + + /** + * Unregister all the providers registered by a particular plugin. + * + * @param plugin The plugin + */ + public void unregisterAll(Plugin plugin); + + /** + * Unregister a particular provider for a particular service. + * + * @param service The service interface + * @param provider The service provider implementation + */ + public void unregister(Class service, Object provider); + + /** + * Unregister a particular provider. + * + * @param provider The service provider implementation + */ + public void unregister(Object provider); + + /** + * Queries for a provider. This may return if no provider has been + * registered for a service. The highest priority provider is returned. + * + * @param The service interface + * @param service The service interface + * @return provider or null + */ + public T load(Class service); + + /** + * Queries for a provider registration. This may return if no provider + * has been registered for a service. + * + * @param The service interface + * @param service The service interface + * @return provider registration or null + */ + public RegisteredServiceProvider getRegistration(Class service); + + /** + * Get registrations of providers for a plugin. + * + * @param plugin The plugin + * @return provider registration or null + */ + public List> getRegistrations(Plugin plugin); + + /** + * Get registrations of providers for a service. The returned list is + * unmodifiable. + * + * @param The service interface + * @param service The service interface + * @return list of registrations + */ + public Collection> getRegistrations(Class service); + + /** + * Get a list of known services. A service is known if it has registered + * providers for it. + * + * @return list of known services + */ + public Collection> getKnownServices(); + + /** + * Returns whether a provider has been registered for a service. Do not + * check this first only to call load(service) later, as that + * would be a non-thread safe situation. + * + * @param service + * @param service service to check + * @return whether there has been a registered provider + */ + public boolean isProvidedFor(Class service); + +} diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java new file mode 100644 index 00000000..b1f5189f --- /dev/null +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -0,0 +1,750 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.PluginCommandYamlParser; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.util.FileUtil; + +import com.google.common.collect.ImmutableSet; + +/** + * Handles all plugin management from the Server + */ +public final class SimplePluginManager implements PluginManager { + private final Server server; + private final Map fileAssociations = new HashMap(); + private final List plugins = new ArrayList(); + private final Map lookupNames = new HashMap(); + private File updateDirectory; + private final SimpleCommandMap commandMap; + private final Map permissions = new HashMap(); + private final Map> defaultPerms = new LinkedHashMap>(); + private final Map> permSubs = new HashMap>(); + private final Map> defSubs = new HashMap>(); + private boolean useTimings = false; + + public SimplePluginManager(Server instance, SimpleCommandMap commandMap) { + server = instance; + this.commandMap = commandMap; + + defaultPerms.put(true, new HashSet()); + defaultPerms.put(false, new HashSet()); + } + + /** + * Registers the specified plugin loader + * + * @param loader Class name of the PluginLoader to register + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ + public void registerInterface(Class loader) throws IllegalArgumentException { + PluginLoader instance; + + if (PluginLoader.class.isAssignableFrom(loader)) { + Constructor constructor; + + try { + constructor = loader.getConstructor(Server.class); + instance = constructor.newInstance(server); + } catch (NoSuchMethodException ex) { + String className = loader.getName(); + + throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex); + } catch (Exception ex) { + throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex); + } + } else { + throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName())); + } + + Pattern[] patterns = instance.getPluginFileFilters(); + + synchronized (this) { + for (Pattern pattern : patterns) { + fileAssociations.put(pattern, instance); + } + } + } + + /** + * Loads the plugins contained within the specified directory + * + * @param directory Directory to check for plugins + * @return A list of all plugins loaded + */ + public Plugin[] loadPlugins(File directory) { + Validate.notNull(directory, "Directory cannot be null"); + Validate.isTrue(directory.isDirectory(), "Directory must be a directory"); + + List result = new ArrayList(); + Set filters = fileAssociations.keySet(); + + if (!(server.getUpdateFolder().equals(""))) { + updateDirectory = new File(directory, server.getUpdateFolder()); + } + + Map plugins = new HashMap(); + Set loadedPlugins = new HashSet(); + Map> dependencies = new HashMap>(); + Map> softDependencies = new HashMap>(); + + // This is where it figures out all possible plugins + for (File file : directory.listFiles()) { + PluginLoader loader = null; + for (Pattern filter : filters) { + Matcher match = filter.matcher(file.getName()); + if (match.find()) { + loader = fileAssociations.get(filter); + } + } + + if (loader == null) continue; + + PluginDescriptionFile description = null; + try { + description = loader.getPluginDescription(file); + String name = description.getName(); + if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name"); + continue; + } else if (description.rawName.indexOf(' ') != -1) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': uses the space-character (0x20) in its name"); + continue; + } + } catch (InvalidDescriptionException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + continue; + } + + File replacedFile = plugins.put(description.getName(), file); + if (replacedFile != null) { + server.getLogger().severe(String.format( + "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'", + description.getName(), + file.getPath(), + replacedFile.getPath(), + directory.getPath() + )); + } + + Collection softDependencySet = description.getSoftDepend(); + if (softDependencySet != null && !softDependencySet.isEmpty()) { + if (softDependencies.containsKey(description.getName())) { + // Duplicates do not matter, they will be removed together if applicable + softDependencies.get(description.getName()).addAll(softDependencySet); + } else { + softDependencies.put(description.getName(), new LinkedList(softDependencySet)); + } + } + + Collection dependencySet = description.getDepend(); + if (dependencySet != null && !dependencySet.isEmpty()) { + dependencies.put(description.getName(), new LinkedList(dependencySet)); + } + + Collection loadBeforeSet = description.getLoadBefore(); + if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) { + for (String loadBeforeTarget : loadBeforeSet) { + if (softDependencies.containsKey(loadBeforeTarget)) { + softDependencies.get(loadBeforeTarget).add(description.getName()); + } else { + // softDependencies is never iterated, so 'ghost' plugins aren't an issue + Collection shortSoftDependency = new LinkedList(); + shortSoftDependency.add(description.getName()); + softDependencies.put(loadBeforeTarget, shortSoftDependency); + } + } + } + } + + loadedPlugins.addAll(ImmutableSet.of("Forge")); + + while (!plugins.isEmpty()) { + boolean missingDependency = true; + Iterator> pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (dependencies.containsKey(plugin)) { + Iterator dependencyIterator = dependencies.get(plugin).iterator(); + + while (dependencyIterator.hasNext()) { + String dependency = dependencyIterator.next(); + + // Dependency loaded + if (loadedPlugins.contains(dependency)) { + dependencyIterator.remove(); + + // We have a dependency not found + } else if (!plugins.containsKey(dependency)) { + missingDependency = false; + pluginIterator.remove(); + softDependencies.remove(plugin); + dependencies.remove(plugin); + + server.getLogger().log( + Level.SEVERE, + "Could not load '" + entry.getValue().getPath() + "' in folder '" + directory.getPath() + "'", + new UnknownDependencyException(dependency)); + break; + } + } + + if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) { + dependencies.remove(plugin); + } + } + if (softDependencies.containsKey(plugin)) { + Iterator softDependencyIterator = softDependencies.get(plugin).iterator(); + + while (softDependencyIterator.hasNext()) { + String softDependency = softDependencyIterator.next(); + + // Soft depend is no longer around + if (!plugins.containsKey(softDependency)) { + softDependencyIterator.remove(); + } + } + + if (softDependencies.get(plugin).isEmpty()) { + softDependencies.remove(plugin); + } + } + if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) { + // We're clear to load, no more soft or hard dependencies left + File file = plugins.get(plugin); + pluginIterator.remove(); + missingDependency = false; + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + continue; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + + if (missingDependency) { + // We now iterate over plugins until something loads + // This loop will ignore soft dependencies + pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (!dependencies.containsKey(plugin)) { + softDependencies.remove(plugin); + missingDependency = false; + File file = entry.getValue(); + pluginIterator.remove(); + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + break; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + // We have no plugins left without a depend + if (missingDependency) { + softDependencies.clear(); + dependencies.clear(); + Iterator failedPluginIterator = plugins.values().iterator(); + + while (failedPluginIterator.hasNext()) { + File file = failedPluginIterator.next(); + failedPluginIterator.remove(); + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected"); + } + } + } + } + + org.bukkit.command.defaults.TimingsCommand.timingStart = System.nanoTime(); // Spigot + return result.toArray(new Plugin[result.size()]); + } + + /** + * Loads the plugin in the specified file + *

    + * File must be valid according to the current enabled Plugin interfaces + * + * @param file File containing the plugin to load + * @return The Plugin loaded, or null if it was invalid + * @throws InvalidPluginException Thrown when the specified file is not a + * valid plugin + * @throws UnknownDependencyException If a required dependency could not + * be found + */ + public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException { + Validate.notNull(file, "File cannot be null"); + + checkUpdate(file); + + Set filters = fileAssociations.keySet(); + Plugin result = null; + + for (Pattern filter : filters) { + String name = file.getName(); + Matcher match = filter.matcher(name); + + if (match.find()) { + PluginLoader loader = fileAssociations.get(filter); + + result = loader.loadPlugin(file); + } + } + + if (result != null) { + plugins.add(result); + lookupNames.put(result.getDescription().getName(), result); + } + + return result; + } + + private void checkUpdate(File file) { + if (updateDirectory == null || !updateDirectory.isDirectory()) { + return; + } + + File updateFile = new File(updateDirectory, file.getName()); + if (updateFile.isFile() && FileUtil.copy(updateFile, file)) { + updateFile.delete(); + } + } + + /** + * Checks if the given plugin is loaded and returns it when applicable + *

    + * Please note that the name of the plugin is case-sensitive + * + * @param name Name of the plugin to check + * @return Plugin if it exists, otherwise null + */ + public synchronized Plugin getPlugin(String name) { + return lookupNames.get(name.replace(' ', '_')); + } + + public synchronized Plugin[] getPlugins() { + return plugins.toArray(new Plugin[plugins.size()]); + } + + /** + * Checks if the given plugin is enabled or not + *

    + * Please note that the name of the plugin is case-sensitive. + * + * @param name Name of the plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(String name) { + Plugin plugin = getPlugin(name); + + return isPluginEnabled(plugin); + } + + /** + * Checks if the given plugin is enabled or not + * + * @param plugin Plugin to check + * @return true if the plugin is enabled, otherwise false + */ + @Override + public synchronized boolean isPluginEnabled(Plugin plugin) { // Paper - synchronize + if ((plugin != null) && (plugins.contains(plugin))) { + return plugin.isEnabled(); + } else { + return false; + } + } + + @Override + public synchronized void enablePlugin(final Plugin plugin) { // Paper - synchronize + if (!plugin.isEnabled()) { + List pluginCommands = PluginCommandYamlParser.parse(plugin); + + if (!pluginCommands.isEmpty()) { + commandMap.registerAll(plugin.getDescription().getName(), pluginCommands); + } + + try { + plugin.getPluginLoader().enablePlugin(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + HandlerList.bakeAll(); + } + } + + // Paper start - close Classloader on disable + @Override + public void disablePlugins() { + disablePlugins(false); + } + + public void disablePlugins(boolean closeClassloaders) { + // Paper end - close Classloader on disable + Plugin[] plugins = getPlugins(); + for (int i = plugins.length - 1; i >= 0; i--) { + disablePlugin(plugins[i], closeClassloaders); // Paper - close Classloader on disable + } + } + + // Paper start - close Classloader on disable + @Override + public void disablePlugin(Plugin plugin) { + disablePlugin(plugin, false); + } + + @Override + public synchronized void disablePlugin(final Plugin plugin, boolean closeClassloader) { // Paper - synchronize + // Paper end - close Classloader on disable + if (plugin.isEnabled()) { + try { + plugin.getPluginLoader().disablePlugin(plugin, closeClassloader); // Paper - close Classloader on disable + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + try { + server.getScheduler().cancelTasks(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while cancelling tasks for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + try { + server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering services for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + try { + HandlerList.unregisterAll(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering events for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + try { + server.getMessenger().unregisterIncomingPluginChannel(plugin); + server.getMessenger().unregisterOutgoingPluginChannel(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering plugin channels for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + } + } + + public void clearPlugins() { + synchronized (this) { + disablePlugins(true); // Paper - close Classloader on disable + plugins.clear(); + lookupNames.clear(); + HandlerList.unregisterAll(); + fileAssociations.clear(); + permissions.clear(); + defaultPerms.get(true).clear(); + defaultPerms.get(false).clear(); + } + } + + private void fireEvent(Event event) { callEvent(event); } // Paper - support old method incase plugin uses reflection + + /** + * Calls an event with the given details. + *

    + * This method only synchronizes when the event is not asynchronous. + * + * @param event Event details + */ + @Override + public void callEvent(Event event) { + // Paper - replace callEvent by merging to below method + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; + } + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + server.getLogger().log(Level.SEVERE, String.format( + "Nag author(s): '%s' of '%s' about the following: %s", + plugin.getDescription().getAuthors(), + plugin.getDescription().getFullName(), + ex.getMessage() + )); + } + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(), ex); + } + } + } + + public void registerEvents(Listener listener, Plugin plugin) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); + } + + for (Map.Entry, Set> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) { + getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); + } + + } + + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) { + registerEvent(event, listener, priority, executor, plugin, false); + } + + /** + * Registers the given event to the specified listener using a directly + * passed EventExecutor + * + * @param event Event class to register + * @param listener PlayerListener to register + * @param priority Priority of this event + * @param executor EventExecutor to register + * @param plugin Plugin to register + * @param ignoreCancelled Do not call executor if event was already + * cancelled + */ + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) { + Validate.notNull(listener, "Listener cannot be null"); + Validate.notNull(priority, "Priority cannot be null"); + Validate.notNull(executor, "Executor cannot be null"); + Validate.notNull(plugin, "Plugin cannot be null"); + + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + + if (useTimings) { + getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } else { + getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } + } + + private HandlerList getEventListeners(Class type) { + try { + Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList"); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception e) { + throw new IllegalPluginAccessException(e.toString()); + } + } + + private Class getRegistrationClass(Class clazz) { + try { + clazz.getDeclaredMethod("getHandlerList"); + return clazz; + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null + && !clazz.getSuperclass().equals(Event.class) + && Event.class.isAssignableFrom(clazz.getSuperclass())) { + return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); + } else { + throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); + } + } + } + + public Permission getPermission(String name) { + return permissions.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void addPermission(Permission perm) { + addPermission(perm, true); + } + + public void addPermission(Permission perm, boolean dirty) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + if (permissions.containsKey(name)) { + throw new IllegalArgumentException("The permission " + name + " is already defined!"); + } + + permissions.put(name, perm); + calculatePermissionDefault(perm, dirty); + } + + public Set getDefaultPermissions(boolean op) { + return ImmutableSet.copyOf(defaultPerms.get(op)); + } + + public void removePermission(Permission perm) { + removePermission(perm.getName()); + } + + public void removePermission(String name) { + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void recalculatePermissionDefaults(Permission perm) { + if (perm != null && permissions.containsKey(perm.getName().toLowerCase(java.util.Locale.ENGLISH))) { + defaultPerms.get(true).remove(perm); + defaultPerms.get(false).remove(perm); + + calculatePermissionDefault(perm, true); + } + } + + private void calculatePermissionDefault(Permission perm, boolean dirty) { + if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(true).add(perm); + if (dirty) { + dirtyPermissibles(true); + } + } + if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(false).add(perm); + if (dirty) { + dirtyPermissibles(false); + } + } + } + + + public void dirtyPermissibles() { + dirtyPermissibles(true); + dirtyPermissibles(false); + } + + private void dirtyPermissibles(boolean op) { + Set permissibles = getDefaultPermSubscriptions(op); + + for (Permissible p : permissibles) { + p.recalculatePermissions(); + } + } + + public void subscribeToPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + map = new WeakHashMap(); + permSubs.put(name, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + permSubs.remove(name); + } + } + } + + public Set getPermissionSubscriptions(String permission) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public void subscribeToDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map == null) { + map = new WeakHashMap(); + defSubs.put(op, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + defSubs.remove(op); + } + } + } + + public Set getDefaultPermSubscriptions(boolean op) { + Map map = defSubs.get(op); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public Set getPermissions() { + return new HashSet(permissions.values()); + } + + public boolean useTimings() { + return useTimings; + } + + /** + * Sets whether or not per event timing code should be used + * + * @param use True if per event timing code should be used + */ + public void useTimings(boolean use) { + useTimings = use; + } +} diff --git a/src/main/java/org/bukkit/plugin/SimpleServicesManager.java b/src/main/java/org/bukkit/plugin/SimpleServicesManager.java new file mode 100644 index 00000000..4e177112 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/SimpleServicesManager.java @@ -0,0 +1,306 @@ +package org.bukkit.plugin; + +import org.bukkit.Bukkit; +import org.bukkit.event.server.ServiceRegisterEvent; +import org.bukkit.event.server.ServiceUnregisterEvent; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A simple services manager. + */ +public class SimpleServicesManager implements ServicesManager { + + /** + * Map of providers. + */ + private final Map, List>> providers = new HashMap, List>>(); + + /** + * Register a provider of a service. + * + * @param Provider + * @param service service class + * @param provider provider to register + * @param plugin plugin with the provider + * @param priority priority of the provider + */ + public void register(Class service, T provider, Plugin plugin, ServicePriority priority) { + RegisteredServiceProvider registeredProvider = null; + synchronized (providers) { + List> registered = providers.get(service); + if (registered == null) { + registered = new ArrayList>(); + providers.put(service, registered); + } + + registeredProvider = new RegisteredServiceProvider(service, provider, priority, plugin); + + // Insert the provider into the collection, much more efficient big O than sort + int position = Collections.binarySearch(registered, registeredProvider); + if (position < 0) { + registered.add(-(position + 1), registeredProvider); + } else { + registered.add(position, registeredProvider); + } + + } + Bukkit.getServer().getPluginManager().callEvent(new ServiceRegisterEvent(registeredProvider)); + } + + /** + * Unregister all the providers registered by a particular plugin. + * + * @param plugin The plugin + */ + public void unregisterAll(Plugin plugin) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getPlugin().equals(plugin)) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Unregister a particular provider for a particular service. + * + * @param service The service interface + * @param provider The service provider implementation + */ + public void unregister(Class service, Object provider) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + + // We want a particular service + if (entry.getKey() != service) { + continue; + } + + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getProvider() == provider) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Unregister a particular provider. + * + * @param provider The service provider implementation + */ + public void unregister(Object provider) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getProvider().equals(provider)) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Queries for a provider. This may return if no provider has been + * registered for a service. The highest priority provider is returned. + * + * @param The service interface + * @param service The service interface + * @return provider or null + */ + public T load(Class service) { + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return null; + } + + // This should not be null! + return service.cast(registered.get(0).getProvider()); + } + } + + /** + * Queries for a provider registration. This may return if no provider + * has been registered for a service. + * + * @param The service interface + * @param service The service interface + * @return provider registration or null + */ + @SuppressWarnings("unchecked") + public RegisteredServiceProvider getRegistration(Class service) { + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return null; + } + + // This should not be null! + return (RegisteredServiceProvider) registered.get(0); + } + } + + /** + * Get registrations of providers for a plugin. + * + * @param plugin The plugin + * @return provider registration or null + */ + public List> getRegistrations(Plugin plugin) { + ImmutableList.Builder> ret = ImmutableList.>builder(); + synchronized (providers) { + for (List> registered : providers.values()) { + for (RegisteredServiceProvider provider : registered) { + if (provider.getPlugin().equals(plugin)) { + ret.add(provider); + } + } + } + } + return ret.build(); + } + + /** + * Get registrations of providers for a service. The returned list is + * an unmodifiable copy. + * + * @param The service interface + * @param service The service interface + * @return a copy of the list of registrations + */ + @SuppressWarnings("unchecked") + public List> getRegistrations(Class service) { + ImmutableList.Builder> ret; + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return ImmutableList.>of(); + } + + ret = ImmutableList.>builder(); + + for (RegisteredServiceProvider provider : registered) { + ret.add((RegisteredServiceProvider) provider); + } + + } + return ret.build(); + } + + /** + * Get a list of known services. A service is known if it has registered + * providers for it. + * + * @return a copy of the set of known services + */ + public Set> getKnownServices() { + synchronized (providers) { + return ImmutableSet.>copyOf(providers.keySet()); + } + } + + /** + * Returns whether a provider has been registered for a service. + * + * @param service + * @param service service to check + * @return true if and only if there are registered providers + */ + public boolean isProvidedFor(Class service) { + synchronized (providers) { + return providers.containsKey(service); + } + } +} diff --git a/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java b/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java new file mode 100644 index 00000000..164be937 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java @@ -0,0 +1,97 @@ +package org.bukkit.plugin; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +/** + * Extends RegisteredListener to include timing information + */ +public class TimedRegisteredListener extends RegisteredListener { + private int count; + private long totalTime; + private Class eventClass; + private boolean multiple = false; + + public TimedRegisteredListener(final Listener pluginListener, final EventExecutor eventExecutor, final EventPriority eventPriority, final Plugin registeredPlugin, final boolean listenCancelled) { + super(pluginListener, eventExecutor, eventPriority, registeredPlugin, listenCancelled); + } + + @Override + public void callEvent(Event event) throws EventException { + if (event.isAsynchronous()) { + super.callEvent(event); + return; + } + count++; + Class newEventClass = event.getClass(); + if (this.eventClass == null) { + this.eventClass = newEventClass; + } else if (!this.eventClass.equals(newEventClass)) { + multiple = true; + this.eventClass = getCommonSuperclass(newEventClass, this.eventClass).asSubclass(Event.class); + } + long start = System.nanoTime(); + super.callEvent(event); + totalTime += System.nanoTime() - start; + } + + private static Class getCommonSuperclass(Class class1, Class class2) { + while (!class1.isAssignableFrom(class2)) { + class1 = class1.getSuperclass(); + } + return class1; + } + + /** + * Resets the call count and total time for this listener + */ + public void reset() { + count = 0; + totalTime = 0; + } + + /** + * Gets the total times this listener has been called + * + * @return Times this listener has been called + */ + public int getCount() { + return count; + } + + /** + * Gets the total time calls to this listener have taken + * + * @return Total time for all calls of this listener + */ + public long getTotalTime() { + return totalTime; + } + + /** + * Gets the class of the events this listener handled. If it handled + * multiple classes of event, the closest shared superclass will be + * returned, such that for any event this listener has handled, + * this.getEventClass().isAssignableFrom(event.getClass()) + * and no class this.getEventClass().isAssignableFrom(clazz) + * {@literal && this.getEventClass() != clazz &&} + * event.getClass().isAssignableFrom(clazz) for all handled events. + * + * @return the event class handled by this RegisteredListener + */ + public Class getEventClass() { + return eventClass; + } + + /** + * Gets whether this listener has handled multiple events, such that for + * some two events, eventA.getClass() != eventB.getClass(). + * + * @return true if this listener has handled multiple events + */ + public boolean hasMultiple() { + return multiple; + } +} diff --git a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java new file mode 100644 index 00000000..a80251ef --- /dev/null +++ b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java @@ -0,0 +1,46 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid Plugin file + */ +public class UnknownDependencyException extends RuntimeException { + + private static final long serialVersionUID = 5721389371901775895L; + + /** + * Constructs a new UnknownDependencyException based on the given + * Exception + * + * @param throwable Exception that triggered this Exception + */ + public UnknownDependencyException(final Throwable throwable) { + super(throwable); + } + + /** + * Constructs a new UnknownDependencyException with the given message + * + * @param message Brief message explaining the cause of the exception + */ + public UnknownDependencyException(final String message) { + super(message); + } + + /** + * Constructs a new UnknownDependencyException based on the given + * Exception + * + * @param message Brief message explaining the cause of the exception + * @param throwable Exception that triggered this Exception + */ + public UnknownDependencyException(final Throwable throwable, final String message) { + super(message, throwable); + } + + /** + * Constructs a new UnknownDependencyException + */ + public UnknownDependencyException() { + + } +} diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java new file mode 100644 index 00000000..f28b9460 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java @@ -0,0 +1,411 @@ +package org.bukkit.plugin.java; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; + +import com.google.common.base.Charsets; + +/** + * Represents a Java plugin + */ +public abstract class JavaPlugin extends PluginBase { + private boolean isEnabled = false; + private PluginLoader loader = null; + private Server server = null; + private File file = null; + private PluginDescriptionFile description = null; + private File dataFolder = null; + private ClassLoader classLoader = null; + private boolean naggable = true; + private FileConfiguration newConfig = null; + private File configFile = null; + private PluginLogger logger = null; + + public JavaPlugin() { + final ClassLoader classLoader = this.getClass().getClassLoader(); + if (!(classLoader instanceof PluginClassLoader)) { + throw new IllegalStateException("JavaPlugin requires " + PluginClassLoader.class.getName()); + } + ((PluginClassLoader) classLoader).initialize(this); + } + + protected JavaPlugin(final JavaPluginLoader loader, final PluginDescriptionFile description, final File dataFolder, final File file) { + final ClassLoader classLoader = this.getClass().getClassLoader(); + if (classLoader instanceof PluginClassLoader) { + throw new IllegalStateException("Cannot use initialization constructor at runtime"); + } + init(loader, loader.server, description, dataFolder, file, classLoader); + } + + /** + * Returns the folder that the plugin data's files are located in. The + * folder may not yet exist. + * + * @return The folder. + */ + @Override + public final File getDataFolder() { + return dataFolder; + } + + /** + * Gets the associated PluginLoader responsible for this plugin + * + * @return PluginLoader that controls this plugin + */ + @Override + public final PluginLoader getPluginLoader() { + return loader; + } + + /** + * Returns the Server instance currently running this plugin + * + * @return Server running this plugin + */ + @Override + public final Server getServer() { + return server; + } + + /** + * Returns a value indicating whether or not this plugin is currently + * enabled + * + * @return true if this plugin is enabled, otherwise false + */ + @Override + public final boolean isEnabled() { + return isEnabled; + } + + /** + * Returns the file which contains this plugin + * + * @return File containing this plugin + */ + protected File getFile() { + return file; + } + + /** + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file + */ + @Override + public final PluginDescriptionFile getDescription() { + return description; + } + + @Override + public FileConfiguration getConfig() { + if (newConfig == null) { + reloadConfig(); + } + return newConfig; + } + + /** + * Provides a reader for a text file located inside the jar. + *

    + * The returned reader will read text with the UTF-8 charset. + * + * @param file the filename of the resource to load + * @return null if {@link #getResource(String)} returns null + * @throws IllegalArgumentException if file is null + * @see ClassLoader#getResourceAsStream(String) + */ + @SuppressWarnings("deprecation") + protected final Reader getTextResource(String file) { + final InputStream in = getResource(file); + + return in == null ? null : new InputStreamReader(in, Charsets.UTF_8); + } + + @SuppressWarnings("deprecation") + @Override + public void reloadConfig() { + newConfig = YamlConfiguration.loadConfiguration(configFile); + + final InputStream defConfigStream = getResource("config.yml"); + if (defConfigStream == null) { + return; + } + + newConfig.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, Charsets.UTF_8))); + } + + @Override + public void saveConfig() { + try { + getConfig().save(configFile); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not save config to " + configFile, ex); + } + } + + @Override + public void saveDefaultConfig() { + if (!configFile.exists()) { + saveResource("config.yml", false); + } + } + + @Override + public void saveResource(String resourcePath, boolean replace) { + if (resourcePath == null || resourcePath.equals("")) { + throw new IllegalArgumentException("ResourcePath cannot be null or empty"); + } + + resourcePath = resourcePath.replace('\\', '/'); + InputStream in = getResource(resourcePath); + if (in == null) { + throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found in " + file); + } + + File outFile = new File(dataFolder, resourcePath); + int lastIndex = resourcePath.lastIndexOf('/'); + File outDir = new File(dataFolder, resourcePath.substring(0, lastIndex >= 0 ? lastIndex : 0)); + + if (!outDir.exists()) { + outDir.mkdirs(); + } + + try { + if (!outFile.exists() || replace) { + OutputStream out = new FileOutputStream(outFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + } else { + logger.log(Level.WARNING, "Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not save " + outFile.getName() + " to " + outFile, ex); + } + } + + @Override + public InputStream getResource(String filename) { + if (filename == null) { + throw new IllegalArgumentException("Filename cannot be null"); + } + + try { + URL url = getClassLoader().getResource(filename); + + if (url == null) { + return null; + } + + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } catch (IOException ex) { + return null; + } + } + + /** + * Returns the ClassLoader which holds this plugin + * + * @return ClassLoader holding this plugin + */ + protected final ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Sets the enabled state of this plugin + * + * @param enabled true if enabled, otherwise false + */ + protected final void setEnabled(final boolean enabled) { + if (isEnabled != enabled) { + isEnabled = enabled; + + if (isEnabled) { + onEnable(); + } else { + onDisable(); + } + } + } + + + final void init(PluginLoader loader, Server server, PluginDescriptionFile description, File dataFolder, File file, ClassLoader classLoader) { + this.loader = loader; + this.server = server; + this.file = file; + this.description = description; + this.dataFolder = dataFolder; + this.classLoader = classLoader; + this.configFile = new File(dataFolder, "config.yml"); + this.logger = new PluginLogger(this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return null; + } + + /** + * Gets the command with the given name, specific to this plugin. Commands + * need to be registered in the {@link PluginDescriptionFile#getCommands() + * PluginDescriptionFile} to exist at runtime. + * + * @param name name or alias of the command + * @return the plugin command if found, otherwise null + */ + public PluginCommand getCommand(String name) { + String alias = name.toLowerCase(java.util.Locale.ENGLISH); + PluginCommand command = getServer().getPluginCommand(alias); + + if (command == null || command.getPlugin() != this) { + command = getServer().getPluginCommand(description.getName().toLowerCase(java.util.Locale.ENGLISH) + ":" + alias); + } + + if (command != null && command.getPlugin() == this) { + return command; + } else { + return null; + } + } + + @Override + public void onLoad() {} + + @Override + public void onDisable() {} + + @Override + public void onEnable() {} + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + return null; + } + + @Override + public final boolean isNaggable() { + return naggable; + } + + @Override + public final void setNaggable(boolean canNag) { + this.naggable = canNag; + } + + @Override + public final Logger getLogger() { + return logger; + } + + @Override + public String toString() { + return description.getFullName(); + } + + /** + * This method provides fast access to the plugin that has {@link + * #getProvidingPlugin(Class) provided} the given plugin class, which is + * usually the plugin that implemented it. + *

    + * An exception to this would be if plugin's jar that contained the class + * does not extend the class, where the intended plugin would have + * resided in a different jar / classloader. + * + * @param a class that extends JavaPlugin + * @param clazz the class desired + * @return the plugin that provides and implements said class + * @throws IllegalArgumentException if clazz is null + * @throws IllegalArgumentException if clazz does not extend {@link + * JavaPlugin} + * @throws IllegalStateException if clazz was not provided by a plugin, + * for example, if called with + * JavaPlugin.getPlugin(JavaPlugin.class) + * @throws IllegalStateException if called from the static initializer for + * given JavaPlugin + * @throws ClassCastException if plugin that provided the class does not + * extend the class + */ + public static T getPlugin(Class clazz) { + Validate.notNull(clazz, "Null class cannot have a plugin"); + if (!JavaPlugin.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException(clazz + " does not extend " + JavaPlugin.class); + } + final ClassLoader cl = clazz.getClassLoader(); + if (!(cl instanceof PluginClassLoader)) { + throw new IllegalArgumentException(clazz + " is not initialized by " + PluginClassLoader.class); + } + JavaPlugin plugin = ((PluginClassLoader) cl).plugin; + if (plugin == null) { + throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer"); + } + return clazz.cast(plugin); + } + + /** + * This method provides fast access to the plugin that has provided the + * given class. + * + * @param clazz a class belonging to a plugin + * @return the plugin that provided the class + * @throws IllegalArgumentException if the class is not provided by a + * JavaPlugin + * @throws IllegalArgumentException if class is null + * @throws IllegalStateException if called from the static initializer for + * given JavaPlugin + */ + public static JavaPlugin getProvidingPlugin(Class clazz) { + Validate.notNull(clazz, "Null class cannot have a plugin"); + final ClassLoader cl = clazz.getClassLoader(); + if (!(cl instanceof PluginClassLoader)) { + throw new IllegalArgumentException(clazz + " is not provided by " + PluginClassLoader.class); + } + JavaPlugin plugin = ((PluginClassLoader) cl).plugin; + if (plugin == null) { + throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer"); + } + return plugin; + } +} diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java new file mode 100644 index 00000000..f3fca2d3 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -0,0 +1,396 @@ +package org.bukkit.plugin.java; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Server; +import org.bukkit.Warning; +import org.bukkit.Warning.WarningState; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.*; +import org.spigotmc.CustomTimingsHandler; // Spigot +import org.yaml.snakeyaml.error.YAMLException; + +/** + * Represents a Java plugin loader, allowing plugins in the form of .jar + */ +public class JavaPluginLoader implements PluginLoader { + Server server; + private Pattern[] fileFilters = new Pattern[] { Pattern.compile("\\.jar$"), }; + private Map> classes = new java.util.concurrent.ConcurrentHashMap>(); // Spigot + private List loaders = new CopyOnWriteArrayList(); + + /** + * This class was not meant to be constructed explicitly + * + * @param instance the server instance + */ + public JavaPluginLoader(Server instance) { + Validate.notNull(instance, "Server cannot be null"); + server = instance; + } + + @Override + public Plugin loadPlugin(File file) throws InvalidPluginException { + Validate.notNull(file, "File cannot be null"); + + if (!file.exists()) { + throw new InvalidPluginException(new FileNotFoundException(file.getPath() + " does not exist")); + } + + PluginDescriptionFile description; + try { + description = getPluginDescription(file); + } catch (InvalidDescriptionException ex) { + throw new InvalidPluginException(ex); + } + + File dataFolder = new File(file.getParentFile(), description.getName()); + File oldDataFolder = getDataFolder(file); + + // Found old data folder + if (dataFolder.equals(oldDataFolder)) { + // They are equal -- nothing needs to be done! + } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) { + server.getLogger().warning(String.format( + "While loading %s (%s) found old-data folder: `%s' next to the new one `%s'", + description.getFullName(), + file, + oldDataFolder, + dataFolder + )); + } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) { + if (!oldDataFolder.renameTo(dataFolder)) { + throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'"); + } + server.getLogger().log(Level.INFO, String.format( + "While loading %s (%s) renamed data folder: `%s' to `%s'", + description.getFullName(), + file, + oldDataFolder, + dataFolder + )); + } + + if (dataFolder.exists() && !dataFolder.isDirectory()) { + throw new InvalidPluginException(String.format( + "Projected datafolder: `%s' for %s (%s) exists and is not a directory", + dataFolder, + description.getFullName(), + file + )); + } + + List depend = description.getDepend(); + if (depend == null) { + depend = ImmutableList.of(); + } + for (String pluginName : depend) { + Plugin current = server.getPluginManager().getPlugin(pluginName); + + if (current == null) { + throw new UnknownDependencyException(pluginName); + } + } + + PluginClassLoader loader; + try { + loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file); + } catch (InvalidPluginException ex) { + throw ex; + } catch (Throwable ex) { + throw new InvalidPluginException(ex); + } + + loaders.add(loader); + + return loader.plugin; + } + + private File getDataFolder(File file) { + File dataFolder = null; + String filename = file.getName(); + int index = file.getName().lastIndexOf("."); + if (index != -1) { + String name = filename.substring(0, index); + dataFolder = new File(file.getParentFile(), name); + } else { + // This is if there is no extension, which should not happen + // Using _ to prevent name collision + + dataFolder = new File(file.getParentFile(), filename + "_"); + } + return dataFolder; + } + + @Override + public PluginDescriptionFile getPluginDescription(File file) throws InvalidDescriptionException { + Validate.notNull(file, "File cannot be null"); + + JarFile jar = null; + InputStream stream = null; + + try { + jar = new JarFile(file); + JarEntry entry = jar.getJarEntry("plugin.yml"); + + if (entry == null) { + throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml")); + } + + stream = jar.getInputStream(entry); + + return new PluginDescriptionFile(stream); + + } catch (IOException ex) { + throw new InvalidDescriptionException(ex); + } catch (YAMLException ex) { + throw new InvalidDescriptionException(ex); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException e) { + } + } + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public Pattern[] getPluginFileFilters() { + return fileFilters.clone(); + } + + Class getClassByName(String name) { + Class cachedClass = classes.get(name); + + if (cachedClass != null) { + return cachedClass; + } else { + for (PluginClassLoader loader : loaders) { + try { + cachedClass = loader.findClass(name, false); + } catch (ClassNotFoundException cnfe) {} + if (cachedClass != null) { + return cachedClass; + } + } + } + return null; + } + + void setClass(String name, Class clazz) { + if (!classes.containsKey(name)) { + classes.put(name, clazz); + + if (ConfigurationSerializable.class.isAssignableFrom(clazz)) { + Class serializable = clazz.asSubclass(ConfigurationSerializable.class); + ConfigurationSerialization.registerClass(serializable); + } + } + } + + private void removeClass(String name) { + Class clazz = classes.remove(name); + + try { + if ((clazz != null) && (ConfigurationSerializable.class.isAssignableFrom(clazz))) { + Class serializable = clazz.asSubclass(ConfigurationSerializable.class); + ConfigurationSerialization.unregisterClass(serializable); + } + } catch (NullPointerException ex) { + // Boggle! + // (Native methods throwing NPEs is not fun when you can't stop it before-hand) + } + } + + @Override + public Map, Set> createRegisteredListeners(Listener listener, Plugin plugin) { + Validate.notNull(plugin, "Plugin can not be null"); + Validate.notNull(listener, "Listener can not be null"); + + Map, Set> ret = new HashMap, Set>(); + Set methods; + try { + Method[] publicMethods = listener.getClass().getMethods(); + Method[] privateMethods = listener.getClass().getDeclaredMethods(); + methods = new HashSet(publicMethods.length + privateMethods.length, 1.0f); + for (Method method : publicMethods) { + methods.add(method); + } + for (Method method : privateMethods) { + methods.add(method); + } + } catch (NoClassDefFoundError e) { + plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist."); + return ret; + } + + for (Method method : methods) { + EventHandler eh = method.getAnnotation(EventHandler.class); + if (eh == null) { + continue; + } + // Do not register bridge or synthetic methods to avoid event duplication + // Fixes SPIGOT-893 + if (method.isBridge() || method.isSynthetic()) { + continue; + } + Class checkClass; + if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) { + plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass()); + continue; + } + Class eventClass = checkClass.asSubclass(Event.class); + method.setAccessible(true); + Set eventSet = ret.get(eventClass); + if (eventSet == null) { + eventSet = new HashSet(); + ret.put(eventClass, eventSet); + } + + for (Class clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { + // This loop checks for extending deprecated events + if (clazz.getAnnotation(Deprecated.class) != null) { + Warning warning = clazz.getAnnotation(Warning.class); + WarningState warningState = server.getWarningState(); + if (!warningState.printFor(warning)) { + break; + } + plugin.getLogger().log( + Level.WARNING, + String.format( + "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." + + " \"%s\"; please notify the authors %s.", + plugin.getDescription().getFullName(), + clazz.getName(), + method.toGenericString(), + (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected", + Arrays.toString(plugin.getDescription().getAuthors().toArray())), + warningState == WarningState.ON ? new AuthorNagException(null) : null); + break; + } + } + + EventExecutor executor; + try { + executor = EventExecutor.create(method, eventClass); + } + catch (Exception e2) { + executor = new EventExecutor1(method, eventClass); + } + // Spigot // Paper - Use factory method `EventExecutor.create()` + eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } + return ret; + } + + public void enablePlugin(final Plugin plugin) { + Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader"); + + if (!plugin.isEnabled()) { + plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName()); + + JavaPlugin jPlugin = (JavaPlugin) plugin; + + PluginClassLoader pluginLoader = (PluginClassLoader) jPlugin.getClassLoader(); + + if (!loaders.contains(pluginLoader)) { + loaders.add(pluginLoader); + server.getLogger().log(Level.WARNING, "Enabled plugin with unregistered PluginClassLoader " + plugin.getDescription().getFullName()); + } + + try { + jPlugin.setEnabled(true); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + server.getPluginManager().disablePlugin(jPlugin, true); // Paper - close Classloader on disable - She's dead jim + return; + // Paper end + } + + // Perhaps abort here, rather than continue going, but as it stands, + // an abort is not possible the way it's currently written + server.getPluginManager().callEvent(new PluginEnableEvent(plugin)); + } + } + + // Paper start - close Classloader on disable + @Override + public void disablePlugin(Plugin plugin) { + disablePlugin(plugin, false); // Retain old behavior unless requested + } + + @Override + public void disablePlugin(Plugin plugin, boolean closeClassloader) { + // Paper end - close Class Loader on disable + Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader"); + + if (plugin.isEnabled()) { + String message = String.format("Disabling %s", plugin.getDescription().getFullName()); + plugin.getLogger().info(message); + + server.getPluginManager().callEvent(new PluginDisableEvent(plugin)); + + JavaPlugin jPlugin = (JavaPlugin) plugin; + ClassLoader cloader = jPlugin.getClassLoader(); + + try { + jPlugin.setEnabled(false); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + if (cloader instanceof PluginClassLoader) { + PluginClassLoader loader = (PluginClassLoader) cloader; + loaders.remove(loader); + + Set names = loader.getClasses(); + + for (String name : names) { + removeClass(name); + } + // Paper start - close Class Loader on disable + try { + if (closeClassloader) { + loader.close(); + } + } catch (IOException e) { + server.getLogger().warning("Error closing the Plugin Class Loader for " + plugin.getDescription().getFullName()); + e.printStackTrace(); + } + // Paper end + } + } + } +} diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java new file mode 100644 index 00000000..5e9b20d4 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -0,0 +1,190 @@ +package org.bukkit.plugin.java; + +import com.google.common.io.ByteStreams; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.apache.commons.lang3.Validate; +import org.bukkit.plugin.InvalidPluginException; +import org.bukkit.plugin.PluginDescriptionFile; + +/** + * A ClassLoader for plugins, to allow shared classes across multiple plugins + */ +final class PluginClassLoader extends URLClassLoader { + private final JavaPluginLoader loader; + private final Map> classes = new java.util.concurrent.ConcurrentHashMap>(); // Spigot + private final PluginDescriptionFile description; + private final File dataFolder; + private final File file; + private final JarFile jar; + private final Manifest manifest; + private final URL url; + final JavaPlugin plugin; + private JavaPlugin pluginInit; + private IllegalStateException pluginState; + + // Spigot Start + static + { + try + { + java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod( "registerAsParallelCapable" ); + if ( method != null ) + { + boolean oldAccessible = method.isAccessible(); + method.setAccessible( true ); + method.invoke( null ); + method.setAccessible( oldAccessible ); + org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.INFO, "Set PluginClassLoader as parallel capable" ); + } + } catch ( NoSuchMethodException ex ) + { + // Ignore + } catch ( Exception ex ) + { + org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Error setting PluginClassLoader as parallel capable", ex ); + } + } + // Spigot End + + PluginClassLoader(final JavaPluginLoader loader, final ClassLoader parent, final PluginDescriptionFile description, final File dataFolder, final File file) throws IOException, InvalidPluginException, MalformedURLException { + super(new URL[] {file.toURI().toURL()}, parent); + Validate.notNull(loader, "Loader cannot be null"); + + this.loader = loader; + this.description = description; + this.dataFolder = dataFolder; + this.file = file; + this.jar = new JarFile(file); + this.manifest = jar.getManifest(); + this.url = file.toURI().toURL(); + + try { + Class jarClass; + try { + jarClass = Class.forName(description.getMain(), true, this); + } catch (ClassNotFoundException ex) { + throw new InvalidPluginException("Cannot find main class `" + description.getMain() + "'", ex); + } + + Class pluginClass; + try { + pluginClass = jarClass.asSubclass(JavaPlugin.class); + } catch (ClassCastException ex) { + throw new InvalidPluginException("main class `" + description.getMain() + "' does not extend JavaPlugin", ex); + } + + plugin = pluginClass.newInstance(); + } catch (IllegalAccessException ex) { + throw new InvalidPluginException("No public constructor", ex); + } catch (InstantiationException ex) { + throw new InvalidPluginException("Abnormal plugin type", ex); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return findClass(name, true); + } + + Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { + if (name.startsWith("org.bukkit.") || name.startsWith("net.minecraft.")) { + throw new ClassNotFoundException(name); + } + Class result = classes.get(name); + + if (result == null) { + if (checkGlobal) { + result = loader.getClassByName(name); + } + + if (result == null) { + String path = name.replace('.', '/').concat(".class"); + JarEntry entry = jar.getJarEntry(path); + + if (entry != null) { + byte[] classBytes; + + try (InputStream is = jar.getInputStream(entry)) { + classBytes = ByteStreams.toByteArray(is); + } catch (IOException ex) { + throw new ClassNotFoundException(name, ex); + } + + int dot = name.lastIndexOf('.'); + if (dot != -1) { + String pkgName = name.substring(0, dot); + if (getPackage(pkgName) == null) { + try { + if (manifest != null) { + definePackage(pkgName, manifest, url); + } else { + definePackage(pkgName, null, null, null, null, null, null, null); + } + } catch (IllegalArgumentException ex) { + if (getPackage(pkgName) == null) { + throw new IllegalStateException("Cannot find package " + pkgName); + } + } + } + } + + CodeSigner[] signers = entry.getCodeSigners(); + CodeSource source = new CodeSource(url, signers); + + result = defineClass(name, classBytes, 0, classBytes.length, source); + } + + if (result == null) { + result = super.findClass(name); + } + + if (result != null) { + loader.setClass(name, result); + } + } + + classes.put(name, result); + } + + return result; + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + jar.close(); + } + } + + Set getClasses() { + return classes.keySet(); + } + + synchronized void initialize(JavaPlugin javaPlugin) { + Validate.notNull(javaPlugin, "Initializing plugin cannot be null"); + Validate.isTrue(javaPlugin.getClass().getClassLoader() == this, "Cannot initialize plugin outside of this class loader"); + if (this.plugin != null || this.pluginInit != null) { + throw new IllegalArgumentException("Plugin already initialized!", pluginState); + } + + pluginState = new IllegalStateException("Initial initialization"); + this.pluginInit = javaPlugin; + + javaPlugin.init(loader, loader.server, description, dataFolder, file, this); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java new file mode 100644 index 00000000..80ef8a2a --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java @@ -0,0 +1,15 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Channel is too long. + */ +@SuppressWarnings("serial") +public class ChannelNameTooLongException extends RuntimeException { + public ChannelNameTooLongException() { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars."); + } + + public ChannelNameTooLongException(String channel) { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars (attempted " + channel.length() + " - '" + channel + "."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java new file mode 100644 index 00000000..2266f176 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java @@ -0,0 +1,15 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin attempts to send a message on an unregistered channel. + */ +@SuppressWarnings("serial") +public class ChannelNotRegisteredException extends RuntimeException { + public ChannelNotRegisteredException() { + this("Attempted to send a plugin message through an unregistered channel."); + } + + public ChannelNotRegisteredException(String channel) { + super("Attempted to send a plugin message through the unregistered channel `" + channel + "'."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java new file mode 100644 index 00000000..61af8c4c --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java @@ -0,0 +1,23 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Message is sent that is too large to be sent. + */ +@SuppressWarnings("serial") +public class MessageTooLargeException extends RuntimeException { + public MessageTooLargeException() { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes."); + } + + public MessageTooLargeException(byte[] message) { + this(message.length); + } + + public MessageTooLargeException(int length) { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes (tried to send one that is " + length + " bytes long)."); + } + + public MessageTooLargeException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/src/main/java/org/bukkit/plugin/messaging/Messenger.java new file mode 100644 index 00000000..655afaf4 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/Messenger.java @@ -0,0 +1,216 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * A class responsible for managing the registrations of plugin channels and + * their listeners. + */ +public interface Messenger { + + /** + * Represents the largest size that an individual Plugin Message may be. + */ + public static final int MAX_MESSAGE_SIZE = 32766; + + /** + * Represents the largest size that a Plugin Channel may be. + */ + public static final int MAX_CHANNEL_SIZE = 20; + + /** + * Checks if the specified channel is a reserved name. + * + * @param channel Channel name to check. + * @return True if the channel is reserved, otherwise false. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public boolean isReservedChannel(String channel); + + /** + * Registers the specific plugin to the requested outgoing plugin channel, + * allowing it to send messages through that channel to any clients. + * + * @param plugin Plugin that wishes to send messages through the channel. + * @param channel Channel to register. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void registerOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from the requested outgoing plugin + * channel, no longer allowing it to send messages through that channel to + * any clients. + * + * @param plugin Plugin that no longer wishes to send messages through the + * channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from all outgoing plugin channels, no + * longer allowing it to send any plugin messages. + * + * @param plugin Plugin that no longer wishes to send plugin messages. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin); + + /** + * Registers the specific plugin for listening on the requested incoming + * plugin channel, allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to register to this channel. + * @param channel Channel to register. + * @param listener Listener to receive messages on. + * @return The resulting registration that was made as a result of this + * method. + * @throws IllegalArgumentException Thrown if plugin, channel or listener + * is null, or the listener is already registered for this channel. + */ + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin's listener from listening on the + * requested incoming plugin channel, no longer allowing it to act upon + * any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @param listener Listener to stop receiving messages on. + * @throws IllegalArgumentException Thrown if plugin, channel or listener + * is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin from listening on the requested + * incoming plugin channel, no longer allowing it to act upon any plugin + * messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from listening on all plugin channels + * through all listeners. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin); + + /** + * Gets a set containing all the outgoing plugin channels. + * + * @return List of all registered outgoing plugin channels. + */ + public Set getOutgoingChannels(); + + /** + * Gets a set containing all the outgoing plugin channels that the + * specified plugin is registered to. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered outgoing plugin channels that a plugin + * is registered to. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getOutgoingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channels. + * + * @return List of all registered incoming plugin channels. + */ + public Set getIncomingChannels(); + + /** + * Gets a set containing all the incoming plugin channels that the + * specified plugin is registered for. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered incoming plugin channels that the plugin + * is registered for. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getIncomingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that the specified plugin has. + * + * @param plugin Plugin to retrieve registrations for. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getIncomingChannelRegistrations(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that are on the requested channel. + * + * @param channel Channel to retrieve registrations for. + * @return List of all registrations that are on the channel. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public Set getIncomingChannelRegistrations(String channel); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that the specified plugin has on the requested channel. + * + * @param plugin Plugin to retrieve registrations for. + * @param channel Channel to filter registrations by. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public Set getIncomingChannelRegistrations(Plugin plugin, String channel); + + /** + * Checks if the specified plugin message listener registration is valid. + *

    + * A registration is considered valid if it has not be unregistered and + * that the plugin is still enabled. + * + * @param registration Registration to check. + * @return True if the registration is valid, otherwise false. + */ + public boolean isRegistrationValid(PluginMessageListenerRegistration registration); + + /** + * Checks if the specified plugin has registered to receive incoming + * messages through the requested channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isIncomingChannelRegistered(Plugin plugin, String channel); + + /** + * Checks if the specified plugin has registered to send outgoing messages + * through the requested channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel); + + /** + * Dispatches the specified incoming message to any registered listeners. + * + * @param source Source of the message. + * @param channel Channel that the message was sent by. + * @param message Raw payload of the message. + */ + public void dispatchIncomingMessage(Player source, String channel, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java b/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java new file mode 100644 index 00000000..3d7ec2e2 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java @@ -0,0 +1,17 @@ +package org.bukkit.plugin.messaging; + +/** + * Represents the different directions a plugin channel may go. + */ +public enum PluginChannelDirection { + + /** + * The plugin channel is being sent to the server from a client. + */ + INCOMING, + + /** + * The plugin channel is being sent to a client from the server. + */ + OUTGOING +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java new file mode 100644 index 00000000..f1aa0806 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java @@ -0,0 +1,20 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; + +/** + * A listener for a specific Plugin Channel, which will receive notifications + * of messages sent from a client. + */ +public interface PluginMessageListener { + + /** + * A method that will be thrown when a PluginMessageSource sends a plugin + * message on a registered channel. + * + * @param channel Channel that the message was sent through. + * @param player Source of the message. + * @param message The raw message that was sent. + */ + public void onPluginMessageReceived(String channel, Player player, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java new file mode 100644 index 00000000..29929bf7 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java @@ -0,0 +1,104 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.plugin.Plugin; + +/** + * Contains information about a {@link Plugin}s registration to a plugin + * channel. + */ +public final class PluginMessageListenerRegistration { + private final Messenger messenger; + private final Plugin plugin; + private final String channel; + private final PluginMessageListener listener; + + public PluginMessageListenerRegistration(Messenger messenger, Plugin plugin, String channel, PluginMessageListener listener) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null!"); + } + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null!"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null!"); + } + + this.messenger = messenger; + this.plugin = plugin; + this.channel = channel; + this.listener = listener; + } + + /** + * Gets the plugin channel that this registration is about. + * + * @return Plugin channel. + */ + public String getChannel() { + return channel; + } + + /** + * Gets the registered listener described by this registration. + * + * @return Registered listener. + */ + public PluginMessageListener getListener() { + return listener; + } + + /** + * Gets the plugin that this registration is for. + * + * @return Registered plugin. + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Checks if this registration is still valid. + * + * @return True if this registration is still valid, otherwise false. + */ + public boolean isValid() { + return messenger.isRegistrationValid(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PluginMessageListenerRegistration other = (PluginMessageListenerRegistration) obj; + if (this.messenger != other.messenger && (this.messenger == null || !this.messenger.equals(other.messenger))) { + return false; + } + if (this.plugin != other.plugin && (this.plugin == null || !this.plugin.equals(other.plugin))) { + return false; + } + if ((this.channel == null) ? (other.channel != null) : !this.channel.equals(other.channel)) { + return false; + } + if (this.listener != other.listener && (this.listener == null || !this.listener.equals(other.listener))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + (this.messenger != null ? this.messenger.hashCode() : 0); + hash = 53 * hash + (this.plugin != null ? this.plugin.hashCode() : 0); + hash = 53 * hash + (this.channel != null ? this.channel.hashCode() : 0); + hash = 53 * hash + (this.listener != null ? this.listener.hashCode() : 0); + return hash; + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java new file mode 100644 index 00000000..e5c59165 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java @@ -0,0 +1,38 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.plugin.Plugin; + +/** + * Represents a possible recipient for a Plugin Message. + */ +public interface PluginMessageRecipient { + /** + * Sends this recipient a Plugin Message on the specified outgoing + * channel. + *

    + * The message may not be larger than {@link Messenger#MAX_MESSAGE_SIZE} + * bytes, and the plugin must be registered to send messages on the + * specified channel. + * + * @param source The plugin that sent this message. + * @param channel The channel to send this message on. + * @param message The raw message to send. + * @throws IllegalArgumentException Thrown if the source plugin is + * disabled. + * @throws IllegalArgumentException Thrown if source, channel or message + * is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNotRegisteredException Thrown if the channel is not + * registered for this plugin. + */ + public void sendPluginMessage(Plugin source, String channel, byte[] message); + + /** + * Gets a set containing all the Plugin Channels that this client is + * listening on. + * + * @return Set containing all the channels that this client may accept. + */ + public Set getListeningPluginChannels(); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java new file mode 100644 index 00000000..0221f049 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java @@ -0,0 +1,16 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a plugin attempts to register for a reserved channel (such as + * "REGISTER") + */ +@SuppressWarnings("serial") +public class ReservedChannelException extends RuntimeException { + public ReservedChannelException() { + this("Attempted to register for a reserved channel name."); + } + + public ReservedChannelException(String name) { + super("Attempted to register for a reserved channel name ('" + name + "')"); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java new file mode 100644 index 00000000..c7ecc9d9 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java @@ -0,0 +1,489 @@ +package org.bukkit.plugin.messaging; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Standard implementation to {@link Messenger} + */ +public class StandardMessenger implements Messenger { + private final Map> incomingByChannel = new HashMap>(); + private final Map> incomingByPlugin = new HashMap>(); + private final Map> outgoingByChannel = new HashMap>(); + private final Map> outgoingByPlugin = new HashMap>(); + private final Object incomingLock = new Object(); + private final Object outgoingLock = new Object(); + + private void addToOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins == null) { + plugins = new HashSet(); + outgoingByChannel.put(channel, plugins); + } + + if (channels == null) { + channels = new HashSet(); + outgoingByPlugin.put(plugin, channels); + } + + plugins.add(plugin); + channels.add(channel); + } + } + + private void removeFromOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins != null) { + plugins.remove(plugin); + + if (plugins.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + + if (channels != null) { + channels.remove(channel); + + if (channels.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + } + } + + private void removeFromOutgoing(Plugin plugin) { + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + String[] toRemove = channels.toArray(new String[channels.size()]); + + outgoingByPlugin.remove(plugin); + + for (String channel : toRemove) { + removeFromOutgoing(plugin, channel); + } + } + } + } + + private void addToIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByChannel.put(registration.getChannel(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByPlugin.put(registration.getPlugin(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + } + } + + private void removeFromIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByChannel.remove(registration.getChannel()); + } + } + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByPlugin.remove(registration.getPlugin()); + } + } + } + } + + private void removeFromIncoming(Plugin plugin, String channel) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[registrations.size()]); + + for (PluginMessageListenerRegistration registration : toRemove) { + if (registration.getChannel().equals(channel)) { + removeFromIncoming(registration); + } + } + } + } + } + + private void removeFromIncoming(Plugin plugin) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[registrations.size()]); + + incomingByPlugin.remove(plugin); + + for (PluginMessageListenerRegistration registration : toRemove) { + removeFromIncoming(registration); + } + } + } + } + + public boolean isReservedChannel(String channel) { + validateChannel(channel); + + return channel.equals("REGISTER") || channel.equals("UNREGISTER"); + } + + public void registerOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + + addToOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromOutgoing(plugin); + } + + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + + PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener); + + addToIncoming(result); + + return result; + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener)); + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(plugin, channel); + } + + public void unregisterIncomingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromIncoming(plugin); + } + + public Set getOutgoingChannels() { + synchronized (outgoingLock) { + Set keys = outgoingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set getOutgoingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return ImmutableSet.copyOf(channels); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannels() { + synchronized (incomingLock) { + Set keys = incomingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set getIncomingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + builder.add(registration.getChannel()); + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(String channel) { + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(channel); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + builder.add(registration); + } + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public boolean isRegistrationValid(PluginMessageListenerRegistration registration) { + if (registration == null) { + throw new IllegalArgumentException("Registration cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + return registrations.contains(registration); + } + + return false; + } + } + + public boolean isIncomingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + return true; + } + } + } + + return false; + } + } + + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return channels.contains(channel); + } + + return false; + } + } + + public void dispatchIncomingMessage(Player source, String channel, byte[] message) { + if (source == null) { + throw new IllegalArgumentException("Player source cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + validateChannel(channel); + + Set registrations = getIncomingChannelRegistrations(channel); + + for (PluginMessageListenerRegistration registration : registrations) { + try { + registration.getListener().onPluginMessageReceived(channel, source, message); + } catch (Throwable t) { + registration.getPlugin().getLogger().log(Level.WARNING, + String.format("Plugin %s generated an exception whilst handling plugin message", + registration.getPlugin().getDescription().getFullName() + ), t); + } + } + } + + /** + * Validates a Plugin Channel name. + * + * @param channel Channel name to validate. + */ + public static void validateChannel(String channel) { + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null"); + } + if (channel.length() > Messenger.MAX_CHANNEL_SIZE) { + throw new ChannelNameTooLongException(channel); + } + } + + /** + * Validates the input of a Plugin Message, ensuring the arguments are all + * valid. + * + * @param messenger Messenger to use for validation. + * @param source Source plugin of the Message. + * @param channel Plugin Channel to send the message by. + * @param message Raw message payload to send. + * @throws IllegalArgumentException Thrown if the source plugin is + * disabled. + * @throws IllegalArgumentException Thrown if source, channel or message + * is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNameTooLongException Thrown if the channel name is too + * long. + * @throws ChannelNotRegisteredException Thrown if the channel is not + * registered for this plugin. + */ + public static void validatePluginMessage(Messenger messenger, Plugin source, String channel, byte[] message) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null"); + } + if (source == null) { + throw new IllegalArgumentException("Plugin source cannot be null"); + } + if (!source.isEnabled()) { + throw new IllegalArgumentException("Plugin must be enabled to send messages"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + if (!messenger.isOutgoingChannelRegistered(source, channel)) { + throw new ChannelNotRegisteredException(channel); + } + if (message.length > Messenger.MAX_MESSAGE_SIZE) { + throw new MessageTooLargeException(message); + } + validateChannel(channel); + } +} diff --git a/src/main/java/org/bukkit/potion/Potion.java b/src/main/java/org/bukkit/potion/Potion.java new file mode 100644 index 00000000..1ce67d1e --- /dev/null +++ b/src/main/java/org/bukkit/potion/Potion.java @@ -0,0 +1,394 @@ +package org.bukkit.potion; + +import java.util.Collection; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; + +/** + * Potion Adapter for pre-1.9 data values + * see @PotionMeta for 1.9+ + */ +@Deprecated +public class Potion { + private boolean extended = false; + private boolean splash = false; + private int level = 1; + private PotionType type; + + /** + * Construct a new potion of the given type. Unless the type is {@link + * PotionType#WATER}, it will be level one, without extended duration. + * Don't use this constructor to create a no-effect potion other than + * water bottle. + * + * @param type The potion type + * @see #Potion(int) + */ + public Potion(PotionType type) { + Validate.notNull(type, "Null PotionType"); + this.type = type; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + */ + public Potion(PotionType type, int level) { + this(type); + Validate.notNull(type, "Type cannot be null"); + Validate.isTrue(level > 0 && level < 3, "Level must be 1 or 2"); + this.level = level; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + * @param splash Whether it is a splash potion. + * @deprecated In favour of using {@link #Potion(PotionType)} with {@link + * #splash()}. + */ + @Deprecated + public Potion(PotionType type, int level, boolean splash) { + this(type, level); + this.splash = splash; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + * @param splash Whether it is a splash potion. + * @param extended Whether it has an extended duration. + * @deprecated In favour of using {@link #Potion(PotionType)} with {@link + * #extend()} and possibly {@link #splash()}. + */ + @Deprecated + public Potion(PotionType type, int level, boolean splash, boolean extended) { + this(type, level, splash); + this.extended = extended; + } + + /** + * @deprecated + */ + @Deprecated + public Potion(int name) { + this(PotionType.WATER); + } + + /** + * Chain this to the constructor to make the potion a splash potion. + * + * @return The potion. + */ + public Potion splash() { + setSplash(true); + return this; + } + + /** + * Chain this to the constructor to extend the potion's duration. + * + * @return The potion. + */ + public Potion extend() { + setHasExtendedDuration(true); + return this; + } + + /** + * Applies the effects of this potion to the given {@link ItemStack}. The + * ItemStack must be a potion. + * + * @param to The itemstack to apply to + */ + public void apply(ItemStack to) { + Validate.notNull(to, "itemstack cannot be null"); + Validate.isTrue(to.hasItemMeta(), "given itemstack is not a potion"); + Validate.isTrue(to.getItemMeta() instanceof PotionMeta, "given itemstack is not a potion"); + PotionMeta meta = (PotionMeta) to.getItemMeta(); + meta.setBasePotionData(new PotionData(type, extended, level == 2)); + to.setItemMeta(meta); + } + + /** + * Applies the effects that would be applied by this potion to the given + * {@link LivingEntity}. + * + * @see LivingEntity#addPotionEffects(Collection) + * @param to The entity to apply the effects to + */ + public void apply(LivingEntity to) { + Validate.notNull(to, "entity cannot be null"); + to.addPotionEffects(getEffects()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Potion other = (Potion) obj; + return extended == other.extended && splash == other.splash && level == other.level && type == other.type; + } + + /** + * Returns a collection of {@link PotionEffect}s that this {@link Potion} + * would confer upon a {@link LivingEntity}. + * + * @see PotionBrewer#getEffectsFromDamage(int) + * @see Potion#toDamageValue() + * @return The effects that this potion applies + */ + public Collection getEffects() { + return getBrewer().getEffects(type, level == 2, extended); + } + + /** + * Returns the level of this potion. + * + * @return The level of this potion + */ + public int getLevel() { + return level; + } + + /** + * Returns the {@link PotionType} of this potion. + * + * @return The type of this potion + */ + public PotionType getType() { + return type; + } + + /** + * Returns whether this potion has an extended duration. + * + * @return Whether this potion has extended duration + */ + public boolean hasExtendedDuration() { + return extended; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = prime + level; + result = prime * result + (extended ? 1231 : 1237); + result = prime * result + (splash ? 1231 : 1237); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + /** + * Returns whether this potion is a splash potion. + * + * @return Whether this is a splash potion + */ + public boolean isSplash() { + return splash; + } + + /** + * Set whether this potion has extended duration. This will cause the + * potion to have roughly 8/3 more duration than a regular potion. + * + * @param isExtended Whether the potion should have extended duration + */ + public void setHasExtendedDuration(boolean isExtended) { + Validate.isTrue(type == null || !type.isInstant(), "Instant potions cannot be extended"); + extended = isExtended; + } + + /** + * Sets whether this potion is a splash potion. Splash potions can be + * thrown for a radius effect. + * + * @param isSplash Whether this is a splash potion + */ + public void setSplash(boolean isSplash) { + splash = isSplash; + } + + /** + * Sets the {@link PotionType} of this potion. + * + * @param type The new type of this potion + */ + public void setType(PotionType type) { + this.type = type; + } + + /** + * Sets the level of this potion. + * + * @param level The new level of this potion + */ + public void setLevel(int level) { + Validate.notNull(this.type, "No-effect potions don't have a level."); + Validate.isTrue(level > 0 && level <= 2, "Level must be between 1 and 2 for this potion"); + this.level = level; + } + + /** + * Converts this potion to a valid potion damage short, usable for potion + * item stacks. + * + * @return The damage value of this potion + * @deprecated Non-functional + */ + @Deprecated + public short toDamageValue() { + return 0; + } + + /** + * Converts this potion to an {@link ItemStack} with the specified amount + * and a correct damage value. + * + * @param amount The amount of the ItemStack + * @return The created ItemStack + */ + public ItemStack toItemStack(int amount) { + Material material; + if (isSplash()) { + material = Material.SPLASH_POTION; + } else { + material = Material.POTION; + } + ItemStack itemStack = new ItemStack(material, amount); + PotionMeta meta = (PotionMeta) itemStack.getItemMeta(); + meta.setBasePotionData(new PotionData(type, level == 2, extended)); + itemStack.setItemMeta(meta); + return itemStack; + } + + private static PotionBrewer brewer; + + private static final int EXTENDED_BIT = 0x40; + private static final int POTION_BIT = 0xF; + private static final int SPLASH_BIT = 0x4000; + private static final int TIER_BIT = 0x20; + private static final int TIER_SHIFT = 5; + + /** + * + * @param damage the damage value + * @return the produced potion + */ + public static Potion fromDamage(int damage) { + PotionType type; + switch (damage & POTION_BIT) { + case 0: + type = PotionType.WATER; + break; + case 1: + type = PotionType.REGEN; + break; + case 2: + type = PotionType.SPEED; + break; + case 3: + type = PotionType.FIRE_RESISTANCE; + break; + case 4: + type = PotionType.POISON; + break; + case 5: + type = PotionType.INSTANT_HEAL; + break; + case 6: + type = PotionType.NIGHT_VISION; + break; + case 8: + type = PotionType.WEAKNESS; + break; + case 9: + type = PotionType.STRENGTH; + break; + case 10: + type = PotionType.SLOWNESS; + break; + case 11: + type = PotionType.JUMP; + break; + case 12: + type = PotionType.INSTANT_DAMAGE; + break; + case 13: + type = PotionType.WATER_BREATHING; + break; + case 14: + type = PotionType.INVISIBILITY; + break; + default: + type = PotionType.WATER; + } + Potion potion; + if (type == null || type == PotionType.WATER) { + potion = new Potion(PotionType.WATER); + } else { + int level = (damage & TIER_BIT) >> TIER_SHIFT; + level++; + potion = new Potion(type, level); + } + if ((damage & SPLASH_BIT) != 0) { + potion = potion.splash(); + } + if ((damage & EXTENDED_BIT) != 0) { + potion = potion.extend(); + } + return potion; + } + + public static Potion fromItemStack(ItemStack item) { + Validate.notNull(item, "item cannot be null"); + if (item.getType() != Material.POTION) + throw new IllegalArgumentException("item is not a potion"); + return fromDamage(item.getDurability()); + } + + /** + * Returns an instance of {@link PotionBrewer}. + * + * @return An instance of PotionBrewer + */ + public static PotionBrewer getBrewer() { + return brewer; + } + + /** + * Sets the current instance of {@link PotionBrewer}. Generally not to be + * used from within a plugin. + * + * @param other The new PotionBrewer + */ + public static void setPotionBrewer(PotionBrewer other) { + if (brewer != null) + throw new IllegalArgumentException("brewer can only be set internally"); + brewer = other; + } + + /** + * + * @return the name id + * @deprecated Non-functional + */ + @Deprecated + public int getNameId() { + return 0; + } +} diff --git a/src/main/java/org/bukkit/potion/PotionBrewer.java b/src/main/java/org/bukkit/potion/PotionBrewer.java new file mode 100644 index 00000000..40f8d12b --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionBrewer.java @@ -0,0 +1,40 @@ +package org.bukkit.potion; + +import java.util.Collection; + +/** + * Represents a brewer that can create {@link PotionEffect}s. + */ +public interface PotionBrewer { + + /** + * Creates a {@link PotionEffect} from the given {@link PotionEffectType}, + * applying duration modifiers and checks. + * + * @param potion The type of potion + * @param duration The duration in ticks + * @param amplifier The amplifier of the effect + * @return The resulting potion effect + */ + public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier); + + /** + * Returns a collection of {@link PotionEffect} that would be applied from + * a potion with the given data value. + * + * @param damage The data value of the potion + * @return The list of effects + * @deprecated Non-Functional + */ + @Deprecated + public Collection getEffectsFromDamage(int damage); + + /** + * Returns a collection of {@link PotionEffect} that would be applied from + * a potion with the given type. + * + * @param type The type of the potion + * @return The list of effects + */ + public Collection getEffects(PotionType type, boolean upgraded, boolean extended); +} diff --git a/src/main/java/org/bukkit/potion/PotionData.java b/src/main/java/org/bukkit/potion/PotionData.java new file mode 100644 index 00000000..24ea4971 --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionData.java @@ -0,0 +1,85 @@ +package org.bukkit.potion; + +import org.apache.commons.lang3.Validate; + +public final class PotionData { + + private final PotionType type; + private final boolean extended; + private final boolean upgraded; + + /** + * Instantiates a final PotionData object to contain information about a + * Potion + * + * @param type the type of the Potion + * @param extended whether the potion is extended PotionType#isExtendable() + * must be true + * @param upgraded whether the potion is upgraded PotionType#isUpgradable() + * must be true + */ + public PotionData(PotionType type, boolean extended, boolean upgraded) { + Validate.notNull(type, "Potion Type must not be null"); + Validate.isTrue(!upgraded || type.isUpgradeable(), "Potion Type is not upgradable"); + Validate.isTrue(!extended || type.isExtendable(), "Potion Type is not extendable"); + Validate.isTrue(!upgraded || !extended, "Potion cannot be both extended and upgraded"); + this.type = type; + this.extended = extended; + this.upgraded = upgraded; + } + + public PotionData(PotionType type) { + this(type, false, false); + } + + /** + * Gets the type of the potion, Type matches up with each kind of craftable + * potion + * + * @return the potion type + */ + public PotionType getType() { + return type; + } + + /** + * Checks if the potion is in an upgraded state. This refers to whether or + * not the potion is Tier 2, such as Potion of Fire Resistance II. + * + * @return true if the potion is upgraded; + */ + public boolean isUpgraded() { + return upgraded; + } + + /** + * Checks if the potion is in an extended state. This refers to the extended + * duration potions + * + * @return true if the potion is extended + */ + public boolean isExtended() { + return extended; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 23 * hash + (this.extended ? 1 : 0); + hash = 23 * hash + (this.upgraded ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PotionData other = (PotionData) obj; + return (this.upgraded == other.upgraded) && (this.extended == other.extended) && (this.type == other.type); + } +} diff --git a/src/main/java/org/bukkit/potion/PotionEffect.java b/src/main/java/org/bukkit/potion/PotionEffect.java new file mode 100644 index 00000000..e000990e --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionEffect.java @@ -0,0 +1,229 @@ +package org.bukkit.potion; + +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.entity.LivingEntity; + +import com.google.common.collect.ImmutableMap; + +/** + * Represents a potion effect, that can be added to a {@link LivingEntity}. A + * potion effect has a duration that it will last for, an amplifier that will + * enhance its effects, and a {@link PotionEffectType}, that represents its + * effect on an entity. + */ +@SerializableAs("PotionEffect") +public class PotionEffect implements ConfigurationSerializable { + private static final String AMPLIFIER = "amplifier"; + private static final String DURATION = "duration"; + private static final String TYPE = "effect"; + private static final String AMBIENT = "ambient"; + private static final String PARTICLES = "has-particles"; + private final int amplifier; + private final int duration; + private final PotionEffectType type; + private final boolean ambient; + private final boolean particles; + private final Color color; + + /** + * Creates a potion effect. + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + * @param particles the particle status, see {@link PotionEffect#hasParticles()} + * @param color the particle color, see {@link PotionEffect#getColor()} + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, Color color) { + Validate.notNull(type, "effect type cannot be null"); + this.type = type; + this.duration = duration; + this.amplifier = amplifier; + this.ambient = ambient; + this.particles = particles; + this.color = color; + } + + /** + * Creates a potion effect with no defined color. + * + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + * @param particles the particle status, see {@link PotionEffect#hasParticles()} + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles) { + this(type, duration, amplifier, ambient, particles, null); + } + + /** + * Creates a potion effect. Assumes that particles are visible + * + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier, boolean ambient) { + this(type, duration, amplifier, ambient, true); + } + + /** + * Creates a potion effect. Assumes ambient is true. + * + * @param type Effect type + * @param duration measured in ticks + * @param amplifier the amplifier for the effect + * @see PotionEffect#PotionEffect(PotionEffectType, int, int, boolean) + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier) { + this(type, duration, amplifier, true); + } + + /** + * Constructor for deserialization. + * + * @param map the map to deserialize from + */ + public PotionEffect(Map map) { + this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT, false), getBool(map, PARTICLES, true)); + } + + private static PotionEffectType getEffectType(Map map) { + int type = getInt(map, TYPE); + PotionEffectType effect = PotionEffectType.getById(type); + if (effect != null) { + return effect; + } + throw new NoSuchElementException(map + " does not contain " + TYPE); + } + + private static int getInt(Map map, Object key) { + Object num = map.get(key); + if (num instanceof Integer) { + return (Integer) num; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + private static boolean getBool(Map map, Object key, boolean def) { + Object bool = map.get(key); + if (bool instanceof Boolean) { + return (Boolean) bool; + } + return def; + } + + public Map serialize() { + return ImmutableMap.of( + TYPE, type.getId(), + DURATION, duration, + AMPLIFIER, amplifier, + AMBIENT, ambient, + PARTICLES, particles + ); + } + + /** + * Attempts to add the effect represented by this object to the given + * {@link LivingEntity}. + * + * @see LivingEntity#addPotionEffect(PotionEffect) + * @param entity The entity to add this effect to + * @return Whether the effect could be added + */ + public boolean apply(LivingEntity entity) { + return entity.addPotionEffect(this); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PotionEffect)) { + return false; + } + PotionEffect that = (PotionEffect) obj; + return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration && this.particles == that.particles; + } + + /** + * Returns the amplifier of this effect. A higher amplifier means the + * potion effect happens more often over its duration and in some cases + * has more effect on its target. + * + * @return The effect amplifier + */ + public int getAmplifier() { + return amplifier; + } + + /** + * Returns the duration (in ticks) that this effect will run for when + * applied to a {@link LivingEntity}. + * + * @return The duration of the effect + */ + public int getDuration() { + return duration; + } + + /** + * Returns the {@link PotionEffectType} of this effect. + * + * @return The potion type of this effect + */ + public PotionEffectType getType() { + return type; + } + + /** + * Makes potion effect produce more, translucent, particles. + * + * @return if this effect is ambient + */ + public boolean isAmbient() { + return ambient; + } + + /** + * @return whether this effect has particles or not + */ + public boolean hasParticles() { + return particles; + } + + /** + * @return color of this potion's particles. May be null if the potion has no particles or defined color. + */ + public Color getColor() { + return color; + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 31 + type.hashCode(); + hash = hash * 31 + amplifier; + hash = hash * 31 + duration; + hash ^= 0x22222222 >> (ambient ? 1 : -1); + hash ^= 0x22222222 >> (particles ? 1 : -1); + return hash; + } + + @Override + public String toString() { + return type.getName() + (ambient ? ":(" : ":") + duration + "t-x" + amplifier + (ambient ? ")" : ""); + } +} diff --git a/src/main/java/org/bukkit/potion/PotionEffectType.java b/src/main/java/org/bukkit/potion/PotionEffectType.java new file mode 100644 index 00000000..cb57f87b --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionEffectType.java @@ -0,0 +1,298 @@ +package org.bukkit.potion; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Color; + +/** + * Represents a type of potion and its effect on an entity. + */ +public abstract class PotionEffectType { + /** + * Increases movement speed. + */ + public static final PotionEffectType SPEED = new PotionEffectTypeWrapper(1); + + /** + * Decreases movement speed. + */ + public static final PotionEffectType SLOW = new PotionEffectTypeWrapper(2); + + /** + * Increases dig speed. + */ + public static final PotionEffectType FAST_DIGGING = new PotionEffectTypeWrapper(3); + + /** + * Decreases dig speed. + */ + public static final PotionEffectType SLOW_DIGGING = new PotionEffectTypeWrapper(4); + + /** + * Increases damage dealt. + */ + public static final PotionEffectType INCREASE_DAMAGE = new PotionEffectTypeWrapper(5); + + /** + * Heals an entity. + */ + public static final PotionEffectType HEAL = new PotionEffectTypeWrapper(6); + + /** + * Hurts an entity. + */ + public static final PotionEffectType HARM = new PotionEffectTypeWrapper(7); + + /** + * Increases jump height. + */ + public static final PotionEffectType JUMP = new PotionEffectTypeWrapper(8); + + /** + * Warps vision on the client. + */ + public static final PotionEffectType CONFUSION = new PotionEffectTypeWrapper(9); + + /** + * Regenerates health. + */ + public static final PotionEffectType REGENERATION = new PotionEffectTypeWrapper(10); + + /** + * Decreases damage dealt to an entity. + */ + public static final PotionEffectType DAMAGE_RESISTANCE = new PotionEffectTypeWrapper(11); + + /** + * Stops fire damage. + */ + public static final PotionEffectType FIRE_RESISTANCE = new PotionEffectTypeWrapper(12); + + /** + * Allows breathing underwater. + */ + public static final PotionEffectType WATER_BREATHING = new PotionEffectTypeWrapper(13); + + /** + * Grants invisibility. + */ + public static final PotionEffectType INVISIBILITY = new PotionEffectTypeWrapper(14); + + /** + * Blinds an entity. + */ + public static final PotionEffectType BLINDNESS = new PotionEffectTypeWrapper(15); + + /** + * Allows an entity to see in the dark. + */ + public static final PotionEffectType NIGHT_VISION = new PotionEffectTypeWrapper(16); + + /** + * Increases hunger. + */ + public static final PotionEffectType HUNGER = new PotionEffectTypeWrapper(17); + + /** + * Decreases damage dealt by an entity. + */ + public static final PotionEffectType WEAKNESS = new PotionEffectTypeWrapper(18); + + /** + * Deals damage to an entity over time. + */ + public static final PotionEffectType POISON = new PotionEffectTypeWrapper(19); + + /** + * Deals damage to an entity over time and gives the health to the + * shooter. + */ + public static final PotionEffectType WITHER = new PotionEffectTypeWrapper(20); + + /** + * Increases the maximum health of an entity. + */ + public static final PotionEffectType HEALTH_BOOST = new PotionEffectTypeWrapper(21); + + /** + * Increases the maximum health of an entity with health that cannot be + * regenerated, but is refilled every 30 seconds. + */ + public static final PotionEffectType ABSORPTION = new PotionEffectTypeWrapper(22); + + /** + * Increases the food level of an entity each tick. + */ + public static final PotionEffectType SATURATION = new PotionEffectTypeWrapper(23); + + /** + * Outlines the entity so that it can be seen from afar. + */ + public static final PotionEffectType GLOWING = new PotionEffectTypeWrapper(24); + + /** + * Causes the entity to float into the air. + */ + public static final PotionEffectType LEVITATION = new PotionEffectTypeWrapper(25); + + /** + * Loot table luck. + */ + public static final PotionEffectType LUCK = new PotionEffectTypeWrapper(26); + + /** + * Loot table unluck. + */ + public static final PotionEffectType UNLUCK = new PotionEffectTypeWrapper(27); + + private final int id; + + protected PotionEffectType(int id) { + this.id = id; + } + + /** + * Creates a PotionEffect from this PotionEffectType, applying duration + * modifiers and checks. + * + * @see PotionBrewer#createEffect(PotionEffectType, int, int) + * @param duration time in ticks + * @param amplifier the effect's amplifier + * @return a resulting potion effect + */ + public PotionEffect createEffect(int duration, int amplifier) { + return new PotionEffect(this, isInstant() ? 1 : (int) (duration * getDurationModifier()), amplifier); + } + + /** + * Returns the duration modifier applied to effects of this type. + * + * @return duration modifier + */ + public abstract double getDurationModifier(); + + /** + * Returns the unique ID of this type. + * + * @return Unique ID + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Returns the name of this effect type. + * + * @return The name of this effect type + */ + public abstract String getName(); + + /** + * Returns whether the effect of this type happens once, immediately. + * + * @return whether this type is normally instant + */ + public abstract boolean isInstant(); + + /** + * Returns the color of this effect type. + * + * @return the color + */ + public abstract Color getColor(); + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof PotionEffectType)) { + return false; + } + final PotionEffectType other = (PotionEffectType) obj; + if (this.id != other.id) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "PotionEffectType[" + id + ", " + getName() + "]"; + } + + private static final PotionEffectType[] byId = new PotionEffectType[300]; + private static final Map byName = new HashMap(); + // will break on updates. + private static boolean acceptingNew = true; + + /** + * Gets the effect type specified by the unique id. + * + * @param id Unique ID to fetch + * @return Resulting type, or null if not found. + * @deprecated Magic value + */ + @Deprecated + public static PotionEffectType getById(int id) { + if (id >= byId.length || id < 0) + return null; + return byId[id]; + } + + /** + * Gets the effect type specified by the given name. + * + * @param name Name of PotionEffectType to fetch + * @return Resulting PotionEffectType, or null if not found. + */ + public static PotionEffectType getByName(String name) { + Validate.notNull(name, "name cannot be null"); + return byName.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * Registers an effect type with the given object. + *

    + * Generally not to be used from within a plugin. + * + * @param type PotionType to register + */ + public static void registerPotionEffectType(PotionEffectType type) { + if (byId[type.id] != null || byName.containsKey(type.getName().toLowerCase(java.util.Locale.ENGLISH))) { + throw new IllegalArgumentException("Cannot set already-set type"); + } else if (!acceptingNew) { + throw new IllegalStateException( + "No longer accepting new potion effect types (can only be done by the server implementation)"); + } + + byId[type.id] = type; + byName.put(type.getName().toLowerCase(java.util.Locale.ENGLISH), type); + } + + /** + * Stops accepting any effect type registrations. + */ + public static void stopAcceptingRegistrations() { + acceptingNew = false; + } + + /** + * Returns an array of all the registered {@link PotionEffectType}s. + * This array is not necessarily in any particular order and may contain null. + * + * @return Array of types. + */ + public static PotionEffectType[] values() { + return byId.clone(); + } +} diff --git a/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java b/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java new file mode 100644 index 00000000..356a866f --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java @@ -0,0 +1,38 @@ +package org.bukkit.potion; + +import org.bukkit.Color; + +public class PotionEffectTypeWrapper extends PotionEffectType { + protected PotionEffectTypeWrapper(int id) { + super(id); + } + + @Override + public double getDurationModifier() { + return getType().getDurationModifier(); + } + + @Override + public String getName() { + return getType().getName(); + } + + /** + * Get the potion type bound to this wrapper. + * + * @return The potion effect type + */ + public PotionEffectType getType() { + return PotionEffectType.getById(getId()); + } + + @Override + public boolean isInstant() { + return getType().isInstant(); + } + + @Override + public Color getColor() { + return getType().getColor(); + } +} diff --git a/src/main/java/org/bukkit/potion/PotionType.java b/src/main/java/org/bukkit/potion/PotionType.java new file mode 100644 index 00000000..60470b88 --- /dev/null +++ b/src/main/java/org/bukkit/potion/PotionType.java @@ -0,0 +1,101 @@ +package org.bukkit.potion; + +/** + * This enum reflects and matches each potion state that can be obtained from + * the Creative mode inventory + */ +public enum PotionType { + UNCRAFTABLE(null, false, false), + WATER(null, false, false), + MUNDANE(null, false, false), + THICK(null, false, false), + AWKWARD(null, false, false), + NIGHT_VISION(PotionEffectType.NIGHT_VISION, false, true), + INVISIBILITY(PotionEffectType.INVISIBILITY, false, true), + JUMP(PotionEffectType.JUMP, true, true), + FIRE_RESISTANCE(PotionEffectType.FIRE_RESISTANCE, false, true), + SPEED(PotionEffectType.SPEED, true, true), + SLOWNESS(PotionEffectType.SLOW, false, true), + WATER_BREATHING(PotionEffectType.WATER_BREATHING, false, true), + INSTANT_HEAL(PotionEffectType.HEAL, true, false), + INSTANT_DAMAGE(PotionEffectType.HARM, true, false), + POISON(PotionEffectType.POISON, true, true), + REGEN(PotionEffectType.REGENERATION, true, true), + STRENGTH(PotionEffectType.INCREASE_DAMAGE, true, true), + WEAKNESS(PotionEffectType.WEAKNESS, false, true), + LUCK(PotionEffectType.LUCK, false, false); + ; + + private final PotionEffectType effect; + private final boolean upgradeable; + private final boolean extendable; + + PotionType(PotionEffectType effect, boolean upgradeable, boolean extendable) { + this.effect = effect; + this.upgradeable = upgradeable; + this.extendable = extendable; + } + + public PotionEffectType getEffectType() { + return effect; + } + + public boolean isInstant() { + return effect != null && effect.isInstant(); + } + + /** + * Checks if the potion type has an upgraded state. + * This refers to whether or not the potion type can be Tier 2, + * such as Potion of Fire Resistance II. + * + * @return true if the potion type can be upgraded; + */ + public boolean isUpgradeable() { + return upgradeable; + } + + /** + * Checks if the potion type has an extended state. + * This refers to the extended duration potions + * + * @return true if the potion type can be extended + */ + public boolean isExtendable() { + return extendable; + } + + /** + * @deprecated Non-functional + */ + @Deprecated + public int getDamageValue() { + return this.ordinal(); + } + + public int getMaxLevel() { + return upgradeable ? 2 : 1; + } + + /** + * @deprecated Non-functional + */ + @Deprecated + public static PotionType getByDamageValue(int damage) { + return null; + } + + /** + * @deprecated Misleading + */ + @Deprecated + public static PotionType getByEffect(PotionEffectType effectType) { + if (effectType == null) + return WATER; + for (PotionType type : PotionType.values()) { + if (effectType.equals(type.effect)) + return type; + } + return null; + } +} diff --git a/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java new file mode 100644 index 00000000..e713c0d8 --- /dev/null +++ b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java @@ -0,0 +1,13 @@ +package org.bukkit.projectiles; + +import org.bukkit.block.Block; + +public interface BlockProjectileSource extends ProjectileSource { + + /** + * Gets the block this projectile source belongs to. + * + * @return Block for the projectile source + */ + public Block getBlock(); +} diff --git a/src/main/java/org/bukkit/projectiles/ProjectileSource.java b/src/main/java/org/bukkit/projectiles/ProjectileSource.java new file mode 100644 index 00000000..cf909461 --- /dev/null +++ b/src/main/java/org/bukkit/projectiles/ProjectileSource.java @@ -0,0 +1,30 @@ +package org.bukkit.projectiles; + +import org.bukkit.entity.Projectile; +import org.bukkit.util.Vector; + +/** + * Represents a valid source of a projectile. + */ +public interface ProjectileSource { + + /** + * Launches a {@link Projectile} from the ProjectileSource. + * + * @param a projectile subclass + * @param projectile class of the projectile to launch + * @return the launched projectile + */ + public T launchProjectile(Class projectile); + + /** + * Launches a {@link Projectile} from the ProjectileSource with an + * initial velocity. + * + * @param a projectile subclass + * @param projectile class of the projectile to launch + * @param velocity the velocity with which to launch + * @return the launched projectile + */ + public T launchProjectile(Class projectile, Vector velocity); +} diff --git a/src/main/java/org/bukkit/scheduler/BukkitRunnable.java b/src/main/java/org/bukkit/scheduler/BukkitRunnable.java new file mode 100644 index 00000000..d526f0f8 --- /dev/null +++ b/src/main/java/org/bukkit/scheduler/BukkitRunnable.java @@ -0,0 +1,163 @@ +package org.bukkit.scheduler; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +/** + * This class is provided as an easy way to handle scheduling tasks. + */ +public abstract class BukkitRunnable implements Runnable { + private BukkitTask task; + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized boolean isCancelled() throws IllegalStateException { + checkScheduled(); + return task.isCancelled(); + } + + /** + * Attempts to cancel this task. + * + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized void cancel() throws IllegalStateException { + Bukkit.getScheduler().cancelTask(getTaskId()); + } + + /** + * Schedules this in the Bukkit scheduler to run on next tick. + * + * @param plugin the reference to the plugin scheduling task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTask(Plugin, Runnable) + */ + public synchronized BukkitTask runTask(Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTask(plugin, (Runnable) this)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this in the Bukkit scheduler to run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskAsynchronously(Plugin, Runnable) + */ + public synchronized BukkitTask runTaskAsynchronously(Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, (Runnable) this)); + } + + /** + * Schedules this to run after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskLater(Plugin, Runnable, long) + */ + public synchronized BukkitTask runTaskLater(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskLater(plugin, (Runnable) this, delay)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this to run asynchronously after the specified number of + * server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskLaterAsynchronously(Plugin, Runnable, long) + */ + public synchronized BukkitTask runTaskLaterAsynchronously(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, (Runnable) this, delay)); + } + + /** + * Schedules this to repeatedly run until cancelled, starting after the + * specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long) + */ + public synchronized BukkitTask runTaskTimer(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskTimer(plugin, (Runnable) this, delay, period)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this to repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskTimerAsynchronously(Plugin, Runnable, long, + * long) + */ + public synchronized BukkitTask runTaskTimerAsynchronously(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, (Runnable) this, delay, period)); + } + + /** + * Gets the task id for this runnable. + * + * @return the task id that this runnable was scheduled as + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized int getTaskId() throws IllegalStateException { + checkScheduled(); + return task.getTaskId(); + } + + private void checkScheduled() { + if (task == null) { + throw new IllegalStateException("Not scheduled yet"); + } + } + + private void checkNotYetScheduled() { + if (task != null) { + throw new IllegalStateException("Already scheduled as " + task.getTaskId()); + } + } + + private BukkitTask setupTask(final BukkitTask task) { + this.task = task; + return task; + } +} diff --git a/src/main/java/org/bukkit/scheduler/BukkitScheduler.java b/src/main/java/org/bukkit/scheduler/BukkitScheduler.java new file mode 100644 index 00000000..6e28205f --- /dev/null +++ b/src/main/java/org/bukkit/scheduler/BukkitScheduler.java @@ -0,0 +1,369 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.List; + +public interface BukkitScheduler { + + /** + * Schedules a once off task to occur after a delay. + *

    + * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncDelayedTask(Plugin plugin, Runnable task, long delay); + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLater(Plugin, long)} + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay); + + /** + * Schedules a once off task to occur as soon as possible. + *

    + * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncDelayedTask(Plugin plugin, Runnable task); + + /** + * @deprecated Use {@link BukkitRunnable#runTask(Plugin)} + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task); + + /** + * Schedules a repeating task. + *

    + * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period); + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)} * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a once off task to occur after a delay. This task will be + * executed by a thread managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task, long delay); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a once off task to occur as soon as possible. This task will + * be executed by a thread managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a repeating task. This task will be executed by a thread + * managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period); + + /** + * Calls a method on the main thread and returns a Future object. This + * task will be executed by the main server thread. + *

      + *
    • Note: The Future.get() methods must NOT be called from the main + * thread. + *
    • Note2: There is at least an average of 10ms latency until the + * isDone() method returns true. + *
    + * @param The callable's return type + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Future Future object related to the task + */ + public Future callSyncMethod(Plugin plugin, Callable task); + + /** + * Removes task from scheduler. + * + * @param taskId Id number of task to be removed + */ + public void cancelTask(int taskId); + + /** + * Removes all tasks associated with a particular plugin from the + * scheduler. + * + * @param plugin Owner of tasks to be removed + */ + public void cancelTasks(Plugin plugin); + + /** + * Removes all tasks from the scheduler. + */ + public void cancelAllTasks(); + + /** + * Check if the task currently running. + *

    + * A repeating task might not be running currently, but will be running in + * the future. A task that has finished, and does not repeat, will not be + * running ever again. + *

    + * Explicitly, a task is running if there exists a thread for it, and that + * thread is alive. + * + * @param taskId The task to check. + *

    + * @return If the task is currently running. + */ + public boolean isCurrentlyRunning(int taskId); + + /** + * Check if the task queued to be run later. + *

    + * If a repeating task is currently running, it might not be queued now + * but could be in the future. A task that is not queued, and not running, + * will not be queued again. + * + * @param taskId The task to check. + *

    + * @return If the task is queued to be run. + */ + public boolean isQueued(int taskId); + + /** + * Returns a list of all active workers. + *

    + * This list contains asynch tasks that are being executed by separate + * threads. + * + * @return Active workers + */ + public List getActiveWorkers(); + + /** + * Returns a list of all pending tasks. The ordering of the tasks is not + * related to their order of execution. + * + * @return Active workers + */ + public List getPendingTasks(); + + /** + * Returns a task that will run on the next server tick. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTask(Plugin plugin, Runnable task) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTask(Plugin)} + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable task) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskAsynchronously(Plugin)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException; + + /** + * Returns a task that will run after the specified number of server + * ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTaskLater(Plugin plugin, Runnable task, long delay) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLater(Plugin, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously after the specified number + * of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable task, long delay) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLaterAsynchronously(Plugin, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException; + + /** + * Returns a task that will repeatedly run until cancelled, starting after + * the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTaskTimer(Plugin plugin, Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/scheduler/BukkitTask.java b/src/main/java/org/bukkit/scheduler/BukkitTask.java new file mode 100644 index 00000000..9c693f83 --- /dev/null +++ b/src/main/java/org/bukkit/scheduler/BukkitTask.java @@ -0,0 +1,42 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; + +/** + * Represents a task being executed by the scheduler + */ +public interface BukkitTask { + + /** + * Returns the taskId for the task. + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Plugin that owns this task. + * + * @return The Plugin that owns the task + */ + public Plugin getOwner(); + + /** + * Returns true if the Task is a sync task. + * + * @return true if the task is run by main thread + */ + public boolean isSync(); + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + */ + public boolean isCancelled(); + + /** + * Will attempt to cancel this task. + */ + public void cancel(); +} diff --git a/src/main/java/org/bukkit/scheduler/BukkitWorker.java b/src/main/java/org/bukkit/scheduler/BukkitWorker.java new file mode 100644 index 00000000..fe1afbd5 --- /dev/null +++ b/src/main/java/org/bukkit/scheduler/BukkitWorker.java @@ -0,0 +1,34 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; + +/** + * Represents a worker thread for the scheduler. This gives information about + * the Thread object for the task, owner of the task and the taskId. + *

    + * Workers are used to execute async tasks. + */ +public interface BukkitWorker { + + /** + * Returns the taskId for the task being executed by this worker. + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Plugin that owns this task. + * + * @return The Plugin that owns the task + */ + public Plugin getOwner(); + + /** + * Returns the thread for the worker. + * + * @return The Thread object for the worker + */ + public Thread getThread(); + +} diff --git a/src/main/java/org/bukkit/scoreboard/Criterias.java b/src/main/java/org/bukkit/scoreboard/Criterias.java new file mode 100644 index 00000000..cd81c87f --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Criterias.java @@ -0,0 +1,20 @@ +package org.bukkit.scoreboard; + +/** + * Criteria names which trigger an objective to be modified by actions in-game + */ +public class Criterias { + public static final String HEALTH; + public static final String PLAYER_KILLS; + public static final String TOTAL_KILLS; + public static final String DEATHS; + + static { + HEALTH="health"; + PLAYER_KILLS="playerKillCount"; + TOTAL_KILLS="totalKillCount"; + DEATHS="deathCount"; + } + + private Criterias() {} +} diff --git a/src/main/java/org/bukkit/scoreboard/DisplaySlot.java b/src/main/java/org/bukkit/scoreboard/DisplaySlot.java new file mode 100644 index 00000000..5d58a18b --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/DisplaySlot.java @@ -0,0 +1,10 @@ +package org.bukkit.scoreboard; + +/** + * Locations for displaying objectives to the player + */ +public enum DisplaySlot { + BELOW_NAME, + PLAYER_LIST, + SIDEBAR; +} diff --git a/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java b/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java new file mode 100644 index 00000000..d9e9ae1a --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java @@ -0,0 +1,25 @@ +package org.bukkit.scoreboard; + +/** + * @deprecated replaced by {@link Team.OptionStatus} + */ +@Deprecated +public enum NameTagVisibility { + + /** + * Always show the player's nametag. + */ + ALWAYS, + /** + * Never show the player's nametag. + */ + NEVER, + /** + * Show the player's nametag only to his own team members. + */ + HIDE_FOR_OTHER_TEAMS, + /** + * Show the player's nametag only to members of other teams. + */ + HIDE_FOR_OWN_TEAM; +} diff --git a/src/main/java/org/bukkit/scoreboard/Objective.java b/src/main/java/org/bukkit/scoreboard/Objective.java new file mode 100644 index 00000000..321aac79 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Objective.java @@ -0,0 +1,110 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; + +/** + * An objective on a scoreboard that can show scores specific to entries. This + * objective is only relevant to the display of the associated {@link + * #getScoreboard() scoreboard}. + */ +public interface Objective { + + /** + * Gets the name of this Objective + * + * @return this objective'ss name + * @throws IllegalStateException if this objective has been unregistered + */ + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to players for this objective + * + * @return this objective's display name + * @throws IllegalStateException if this objective has been unregistered + */ + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to players for this objective. + * + * @param displayName Display name to set + * @throws IllegalStateException if this objective has been unregistered + * @throws IllegalArgumentException if displayName is null + * @throws IllegalArgumentException if displayName is longer than 32 + * characters. + */ + void setDisplayName(String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the criteria this objective tracks. + * + * @return this objective's criteria + * @throws IllegalStateException if this objective has been unregistered + */ + String getCriteria() throws IllegalStateException; + + /** + * Gets if the objective's scores can be modified directly by a plugin. + * + * @return true if scores are modifiable + * @throws IllegalStateException if this objective has been unregistered + * @see Criterias#HEALTH + */ + boolean isModifiable() throws IllegalStateException; + + /** + * Gets the scoreboard to which this objective is attached. + * + * @return Owning scoreboard, or null if it has been {@link #unregister() + * unregistered} + */ + Scoreboard getScoreboard(); + + /** + * Unregisters this objective from the {@link Scoreboard scoreboard.} + * + * @throws IllegalStateException if this objective has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Sets this objective to display on the specified slot for the + * scoreboard, removing it from any other display slot. + * + * @param slot display slot to change, or null to not display + * @throws IllegalStateException if this objective has been unregistered + */ + void setDisplaySlot(DisplaySlot slot) throws IllegalStateException; + + /** + * Gets the display slot this objective is displayed at. + * + * @return the display slot for this objective, or null if not displayed + * @throws IllegalStateException if this objective has been unregistered + */ + DisplaySlot getDisplaySlot() throws IllegalStateException; + + /** + * Gets a player's Score for an Objective on this Scoreboard + * + * @param player Player for the Score + * @return Score tracking the Objective and player specified + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this objective has been unregistered + * @deprecated Scoreboards can contain entries that aren't players + * @see #getScore(String) + */ + @Deprecated + Score getScore(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; + + /** + * Gets an entry's Score for an Objective on this Scoreboard. + * + * @param entry Entry for the Score + * @return Score tracking the Objective and entry specified + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this objective has been unregistered + */ + Score getScore(String entry) throws IllegalArgumentException, IllegalStateException; +} diff --git a/src/main/java/org/bukkit/scoreboard/Score.java b/src/main/java/org/bukkit/scoreboard/Score.java new file mode 100644 index 00000000..f7b86b40 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Score.java @@ -0,0 +1,70 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; + +/** + * A score entry for an {@link #getEntry() entry} on an {@link + * #getObjective() objective}. Changing this will not affect any other + * objective or scoreboard. + */ +public interface Score { + + /** + * Gets the OfflinePlayer being tracked by this Score + * + * @return this Score's tracked player + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntry() + */ + @Deprecated + OfflinePlayer getPlayer(); + + /** + * Gets the entry being tracked by this Score + * + * @return this Score's tracked entry + */ + String getEntry(); + + /** + * Gets the Objective being tracked by this Score + * + * @return this Score's tracked objective + */ + Objective getObjective(); + + /** + * Gets the current score + * + * @return the current score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + int getScore() throws IllegalStateException; + + /** + * Sets the current score. + * + * @param score New score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + void setScore(int score) throws IllegalStateException; + + /** + * Shows if this score has been set at any point in time. + * + * @return if this score has been set before + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + boolean isScoreSet() throws IllegalStateException; + + /** + * Gets the scoreboard for the associated objective. + * + * @return the owning objective's scoreboard, or null if it has been + * {@link Objective#unregister() unregistered} + */ + Scoreboard getScoreboard(); +} diff --git a/src/main/java/org/bukkit/scoreboard/Scoreboard.java b/src/main/java/org/bukkit/scoreboard/Scoreboard.java new file mode 100644 index 00000000..70086109 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Scoreboard.java @@ -0,0 +1,171 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.OfflinePlayer; + +/** + * A scoreboard + */ +public interface Scoreboard { + + /** + * Registers an Objective on this Scoreboard + * + * @param name Name of the Objective + * @param criteria Criteria for the Objective + * @return The registered Objective + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if an objective by that name already + * exists + */ + Objective registerNewObjective(String name, String criteria) throws IllegalArgumentException; + + /** + * Gets an Objective on this Scoreboard by name + * + * @param name Name of the Objective + * @return the Objective or null if it does not exist + * @throws IllegalArgumentException if name is null + */ + Objective getObjective(String name) throws IllegalArgumentException; + + /** + * Gets all Objectives of a Criteria on the Scoreboard + * + * @param criteria Criteria to search by + * @return an immutable set of Objectives using the specified Criteria + */ + Set getObjectivesByCriteria(String criteria) throws IllegalArgumentException; + + /** + * Gets all Objectives on this Scoreboard + * + * @return An immutable set of all Objectives on this Scoreboard + */ + Set getObjectives(); + + /** + * Gets the Objective currently displayed in a DisplaySlot on this + * Scoreboard + * + * @param slot The DisplaySlot + * @return the Objective currently displayed or null if nothing is + * displayed in that DisplaySlot + * @throws IllegalArgumentException if slot is null + */ + Objective getObjective(DisplaySlot slot) throws IllegalArgumentException; + + /** + * Gets all scores for a player on this Scoreboard + * + * @param player the player whose scores are being retrieved + * @return immutable set of all scores tracked for the player + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #getScores(String) + */ + @Deprecated + Set getScores(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets all scores for an entry on this Scoreboard + * + * @param entry the entry whose scores are being retrieved + * @return immutable set of all scores tracked for the entry + * @throws IllegalArgumentException if entry is null + */ + Set getScores(String entry) throws IllegalArgumentException; + + /** + * Removes all scores for a player on this Scoreboard + * + * @param player the player to drop all current scores for + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #resetScores(String) + */ + @Deprecated + void resetScores(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Removes all scores for an entry on this Scoreboard + * + * @param entry the entry to drop all current scores for + * @throws IllegalArgumentException if entry is null + */ + void resetScores(String entry) throws IllegalArgumentException; + + /** + * Gets a player's Team on this Scoreboard + * + * @param player the player to search for + * @return the player's Team or null if the player is not on a team + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntryTeam(String) + */ + @Deprecated + Team getPlayerTeam(OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets a entries Team on this Scoreboard + * + * @param entry the entry to search for + * @return the entries Team or null if the entry is not on a team + * @throws IllegalArgumentException if entry is null + */ + Team getEntryTeam(String entry) throws IllegalArgumentException; + + /** + * Gets a Team by name on this Scoreboard + * + * @param teamName Team name + * @return the matching Team or null if no matches + * @throws IllegalArgumentException if teamName is null + */ + Team getTeam(String teamName) throws IllegalArgumentException; + + /** + * Gets all teams on this Scoreboard + * + * @return an immutable set of Teams + */ + Set getTeams(); + + /** + * Registers a Team on this Scoreboard + * + * @param name Team name + * @return registered Team + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if team by that name already exists + */ + Team registerNewTeam(String name) throws IllegalArgumentException; + + /** + * Gets all players tracked by this Scoreboard + * + * @return immutable set of all tracked players + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntries() + */ + @Deprecated + Set getPlayers(); + + /** + * Gets all entries tracked by this Scoreboard + * + * @return immutable set of all tracked entries + */ + Set getEntries(); + + /** + * Clears any objective in the specified slot. + * + * @param slot the slot to remove objectives + * @throws IllegalArgumentException if slot is null + */ + void clearSlot(DisplaySlot slot) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java new file mode 100644 index 00000000..00b67a18 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java @@ -0,0 +1,29 @@ +package org.bukkit.scoreboard; + +import java.lang.ref.WeakReference; + +/** + * Manager of Scoreboards + */ +public interface ScoreboardManager { + + /** + * Gets the primary Scoreboard controlled by the server. + *

    + * This Scoreboard is saved by the server, is affected by the /scoreboard + * command, and is the scoreboard shown by default to players. + * + * @return the default sever scoreboard + */ + Scoreboard getMainScoreboard(); + + /** + * Gets a new Scoreboard to be tracked by the server. This scoreboard will + * be tracked as long as a reference is kept, either by a player or by a + * plugin. + * + * @return the registered Scoreboard + * @see WeakReference + */ + Scoreboard getNewScoreboard(); +} diff --git a/src/main/java/org/bukkit/scoreboard/Team.java b/src/main/java/org/bukkit/scoreboard/Team.java new file mode 100644 index 00000000..28d9fa00 --- /dev/null +++ b/src/main/java/org/bukkit/scoreboard/Team.java @@ -0,0 +1,328 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.potion.PotionEffectType; + +/** + * A team on a scoreboard that has a common display theme and other + * properties. This team is only relevant to the display of the associated + * {@link #getScoreboard() scoreboard}. + */ +public interface Team { + + /** + * Gets the name of this Team + * + * @return Objective name + * @throws IllegalStateException if this team has been unregistered + */ + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to entries for this team + * + * @return Team display name + * @throws IllegalStateException if this team has been unregistered + */ + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to entries for this team + * + * @param displayName New display name + * @throws IllegalArgumentException if displayName is longer than 32 + * characters. + * @throws IllegalStateException if this team has been unregistered + */ + void setDisplayName(String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the prefix prepended to the display of entries on this team. + * + * @return Team prefix + * @throws IllegalStateException if this team has been unregistered + */ + String getPrefix() throws IllegalStateException; + + /** + * Sets the prefix prepended to the display of entries on this team. + * + * @param prefix New prefix + * @throws IllegalArgumentException if prefix is null + * @throws IllegalArgumentException if prefix is longer than 16 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setPrefix(String prefix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the suffix appended to the display of entries on this team. + * + * @return the team's current suffix + * @throws IllegalStateException if this team has been unregistered + */ + String getSuffix() throws IllegalStateException; + + /** + * Sets the suffix appended to the display of entries on this team. + * + * @param suffix the new suffix for this team. + * @throws IllegalArgumentException if suffix is null + * @throws IllegalArgumentException if suffix is longer than 16 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setSuffix(String suffix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the color of the team. + *
    + * This only sets the team outline, other occurrences of colors such as in + * names are handled by prefixes / suffixes. + * + * @return team color, defaults to {@link ChatColor#RESET} + * @throws IllegalStateException + */ + ChatColor getColor() throws IllegalStateException; + + /** + * Sets the color of the team. + *
    + * This only sets the team outline, other occurrences of colors such as in + * names are handled by prefixes / suffixes. + * + * @param color new color, must be non-null. Use {@link ChatColor#RESET} for + * no color + */ + void setColor(ChatColor color); + + /** + * Gets the team friendly fire state + * + * @return true if friendly fire is enabled + * @throws IllegalStateException if this team has been unregistered + */ + boolean allowFriendlyFire() throws IllegalStateException; + + /** + * Sets the team friendly fire state + * + * @param enabled true if friendly fire is to be allowed + * @throws IllegalStateException if this team has been unregistered + */ + void setAllowFriendlyFire(boolean enabled) throws IllegalStateException; + + /** + * Gets the team's ability to see {@link PotionEffectType#INVISIBILITY + * invisible} teammates. + * + * @return true if team members can see invisible members + * @throws IllegalStateException if this team has been unregistered + */ + boolean canSeeFriendlyInvisibles() throws IllegalStateException; + + /** + * Sets the team's ability to see {@link PotionEffectType#INVISIBILITY + * invisible} teammates. + * + * @param enabled true if invisible teammates are to be visible + * @throws IllegalStateException if this team has been unregistered + */ + void setCanSeeFriendlyInvisibles(boolean enabled) throws IllegalStateException; + + /** + * Gets the team's ability to see name tags + * + * @return the current name tag visibility for the team + * @throws IllegalArgumentException if this team has been unregistered + * @deprecated see {@link #getOption(Option)} + */ + @Deprecated + NameTagVisibility getNameTagVisibility() throws IllegalArgumentException; + + /** + * Set's the team's ability to see name tags + * + * @param visibility The nameTagVisibilty to set + * @throws IllegalArgumentException if this team has been unregistered + * @deprecated see + * {@link #setOption(Option, OptionStatus)} + */ + @Deprecated + void setNameTagVisibility(NameTagVisibility visibility) throws IllegalArgumentException; + + /** + * Gets the Set of players on the team + * + * @return players on the team + * @throws IllegalStateException if this team has been unregistered\ + * @deprecated Teams can contain entries that aren't players + * @see #getEntries() + */ + @Deprecated + Set getPlayers() throws IllegalStateException; + + /** + * Gets the Set of entries on the team + * + * @return entries on the team + * @throws IllegalStateException if this entries has been unregistered\ + */ + Set getEntries() throws IllegalStateException; + + /** + * Gets the size of the team + * + * @return number of entries on the team + * @throws IllegalStateException if this team has been unregistered + */ + int getSize() throws IllegalStateException; + + /** + * Gets the Scoreboard to which this team is attached + * + * @return Owning scoreboard, or null if this team has been {@link + * #unregister() unregistered} + */ + Scoreboard getScoreboard(); + + /** + * This puts the specified player onto this team for the scoreboard. + *

    + * This will remove the player from any other team on the scoreboard. + * + * @param player the player to add + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #addEntry(String) + */ + @Deprecated + void addPlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * This puts the specified entry onto this team for the scoreboard. + *

    + * This will remove the entry from any other team on the scoreboard. + * + * @param entry the entry to add + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + */ + void addEntry(String entry) throws IllegalStateException, IllegalArgumentException; + + /** + * Removes the player from this team. + * + * @param player the player to remove + * @return if the player was on this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #removeEntry(String) + */ + @Deprecated + boolean removePlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * Removes the entry from this team. + * + * @param entry the entry to remove + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + * @return if the entry was a part of this team + */ + boolean removeEntry(String entry) throws IllegalStateException, IllegalArgumentException; + + /** + * Unregisters this team from the Scoreboard + * + * @throws IllegalStateException if this team has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Checks to see if the specified player is a member of this team. + * + * @param player the player to search for + * @return true if the player is a member of this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #hasEntry(String) + */ + @Deprecated + boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; + /** + * Checks to see if the specified entry is a member of this team. + * + * @param entry the entry to search for + * @return true if the entry is a member of this team + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + */ + boolean hasEntry(String entry) throws IllegalArgumentException, IllegalStateException; + + /** + * Get an option for this team + * + * @param option the option to get + * @return the option status + * @throws IllegalStateException if this team has been unregistered + */ + OptionStatus getOption(Option option) throws IllegalStateException; + + /** + * Set an option for this team + * + * @param option the option to set + * @param status the new option status + * @throws IllegalStateException if this team has been unregistered + */ + void setOption(Option option, OptionStatus status) throws IllegalStateException; + + /** + * Represents an option which may be applied to this team. + */ + public enum Option { + + /** + * How to display the name tags of players on this team. + */ + NAME_TAG_VISIBILITY, + /** + * How to display the death messages for players on this team. + */ + DEATH_MESSAGE_VISIBILITY, + /** + * How players of this team collide with others. + */ + COLLISION_RULE; + } + + /** + * How an option may be applied to members of this team. + */ + public enum OptionStatus { + + /** + * Apply this option to everyone. + */ + ALWAYS, + /** + * Never apply this option. + */ + NEVER, + /** + * Apply this option only for opposing teams. + */ + FOR_OTHER_TEAMS, + /** + * Apply this option for only team members. + */ + FOR_OWN_TEAM; + } +} diff --git a/src/main/java/org/bukkit/util/BlockIterator.java b/src/main/java/org/bukkit/util/BlockIterator.java new file mode 100644 index 00000000..5c85778c --- /dev/null +++ b/src/main/java/org/bukkit/util/BlockIterator.java @@ -0,0 +1,357 @@ +package org.bukkit.util; + +import static org.bukkit.util.NumberConversions.*; + +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.LivingEntity; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class performs ray tracing and iterates along blocks on a line + */ +public class BlockIterator implements Iterator { + + private final World world; + private final int maxDistance; + + private static final int gridSize = 1 << 24; + + private boolean end = false; + + private Block[] blockQueue = new Block[3]; + private int currentBlock = 0; + private int currentDistance = 0; + private int maxDistanceInt; + + private int secondError; + private int thirdError; + + private int secondStep; + private int thirdStep; + + private BlockFace mainFace; + private BlockFace secondFace; + private BlockFace thirdFace; + + /** + * Constructs the BlockIterator + * + * @param world The world to use for tracing + * @param start A Vector giving the initial location for the trace + * @param direction A Vector pointing in the direction for the trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + * + */ + public BlockIterator(World world, Vector start, Vector direction, double yOffset, int maxDistance) { + this.world = world; + this.maxDistance = maxDistance; + + Vector startClone = start.clone(); + + startClone.setY(startClone.getY() + yOffset); + + currentDistance = 0; + + double mainDirection = 0; + double secondDirection = 0; + double thirdDirection = 0; + + double mainPosition = 0; + double secondPosition = 0; + double thirdPosition = 0; + + Block startBlock = this.world.getBlockAt(floor(startClone.getX()), floor(startClone.getY()), floor(startClone.getZ())); + + if (getXLength(direction) > mainDirection) { + mainFace = getXFace(direction); + mainDirection = getXLength(direction); + mainPosition = getXPosition(direction, startClone, startBlock); + + secondFace = getYFace(direction); + secondDirection = getYLength(direction); + secondPosition = getYPosition(direction, startClone, startBlock); + + thirdFace = getZFace(direction); + thirdDirection = getZLength(direction); + thirdPosition = getZPosition(direction, startClone, startBlock); + } + if (getYLength(direction) > mainDirection) { + mainFace = getYFace(direction); + mainDirection = getYLength(direction); + mainPosition = getYPosition(direction, startClone, startBlock); + + secondFace = getZFace(direction); + secondDirection = getZLength(direction); + secondPosition = getZPosition(direction, startClone, startBlock); + + thirdFace = getXFace(direction); + thirdDirection = getXLength(direction); + thirdPosition = getXPosition(direction, startClone, startBlock); + } + if (getZLength(direction) > mainDirection) { + mainFace = getZFace(direction); + mainDirection = getZLength(direction); + mainPosition = getZPosition(direction, startClone, startBlock); + + secondFace = getXFace(direction); + secondDirection = getXLength(direction); + secondPosition = getXPosition(direction, startClone, startBlock); + + thirdFace = getYFace(direction); + thirdDirection = getYLength(direction); + thirdPosition = getYPosition(direction, startClone, startBlock); + } + + // trace line backwards to find intercept with plane perpendicular to the main axis + + double d = mainPosition / mainDirection; // how far to hit face behind + double secondd = secondPosition - secondDirection * d; + double thirdd = thirdPosition - thirdDirection * d; + + // Guarantee that the ray will pass though the start block. + // It is possible that it would miss due to rounding + // This should only move the ray by 1 grid position + secondError = floor(secondd * gridSize); + secondStep = round(secondDirection / mainDirection * gridSize); + thirdError = floor(thirdd * gridSize); + thirdStep = round(thirdDirection / mainDirection * gridSize); + + if (secondError + secondStep <= 0) { + secondError = -secondStep + 1; + } + + if (thirdError + thirdStep <= 0) { + thirdError = -thirdStep + 1; + } + + Block lastBlock; + + lastBlock = startBlock.getRelative(mainFace.getOppositeFace()); + + if (secondError < 0) { + secondError += gridSize; + lastBlock = lastBlock.getRelative(secondFace.getOppositeFace()); + } + + if (thirdError < 0) { + thirdError += gridSize; + lastBlock = lastBlock.getRelative(thirdFace.getOppositeFace()); + } + + // This means that when the variables are positive, it means that the coord=1 boundary has been crossed + secondError -= gridSize; + thirdError -= gridSize; + + blockQueue[0] = lastBlock; + currentBlock = -1; + + scan(); + + boolean startBlockFound = false; + + for (int cnt = currentBlock; cnt >= 0; cnt--) { + if (blockEquals(blockQueue[cnt], startBlock)) { + currentBlock = cnt; + startBlockFound = true; + break; + } + } + + if (!startBlockFound) { + throw new IllegalStateException("Start block missed in BlockIterator"); + } + + // Calculate the number of planes passed to give max distance + maxDistanceInt = round(maxDistance / (Math.sqrt(mainDirection * mainDirection + secondDirection * secondDirection + thirdDirection * thirdDirection) / mainDirection)); + + } + + private boolean blockEquals(Block a, Block b) { + return a.getX() == b.getX() && a.getY() == b.getY() && a.getZ() == b.getZ(); + } + + private BlockFace getXFace(Vector direction) { + return ((direction.getX() > 0) ? BlockFace.EAST : BlockFace.WEST); + } + + private BlockFace getYFace(Vector direction) { + return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN); + } + + private BlockFace getZFace(Vector direction) { + return ((direction.getZ() > 0) ? BlockFace.SOUTH : BlockFace.NORTH); + } + + private double getXLength(Vector direction) { + return Math.abs(direction.getX()); + } + + private double getYLength(Vector direction) { + return Math.abs(direction.getY()); + } + + private double getZLength(Vector direction) { + return Math.abs(direction.getZ()); + } + + private double getPosition(double direction, double position, int blockPosition) { + return direction > 0 ? (position - blockPosition) : (blockPosition + 1 - position); + } + + private double getXPosition(Vector direction, Vector position, Block block) { + return getPosition(direction.getX(), position.getX(), block.getX()); + } + + private double getYPosition(Vector direction, Vector position, Block block) { + return getPosition(direction.getY(), position.getY(), block.getY()); + } + + private double getZPosition(Vector direction, Vector position, Block block) { + return getPosition(direction.getZ(), position.getZ(), block.getZ()); + } + + /** + * Constructs the BlockIterator + * + * @param loc The location for the start of the ray trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + */ + public BlockIterator(Location loc, double yOffset, int maxDistance) { + this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, maxDistance); + } + + /** + * Constructs the BlockIterator. + * + * @param loc The location for the start of the ray trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + */ + + public BlockIterator(Location loc, double yOffset) { + this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, 0); + } + + /** + * Constructs the BlockIterator. + * + * @param loc The location for the start of the ray trace + */ + + public BlockIterator(Location loc) { + this(loc, 0D); + } + + /** + * Constructs the BlockIterator. + * + * @param entity Information from the entity is used to set up the trace + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + */ + + public BlockIterator(LivingEntity entity, int maxDistance) { + this(entity.getLocation(), entity.getEyeHeight(), maxDistance); + } + + /** + * Constructs the BlockIterator. + * + * @param entity Information from the entity is used to set up the trace + */ + + public BlockIterator(LivingEntity entity) { + this(entity, 0); + } + + /** + * Returns true if the iteration has more elements + */ + + public boolean hasNext() { + scan(); + return currentBlock != -1; + } + + /** + * Returns the next Block in the trace + * + * @return the next Block in the trace + */ + + public Block next() { + scan(); + if (currentBlock <= -1) { + throw new NoSuchElementException(); + } else { + return blockQueue[currentBlock--]; + } + } + + public void remove() { + throw new UnsupportedOperationException("[BlockIterator] doesn't support block removal"); + } + + private void scan() { + if (currentBlock >= 0) { + return; + } + if (maxDistance != 0 && currentDistance > maxDistanceInt) { + end = true; + return; + } + if (end) { + return; + } + + currentDistance++; + + secondError += secondStep; + thirdError += thirdStep; + + if (secondError > 0 && thirdError > 0) { + blockQueue[2] = blockQueue[0].getRelative(mainFace); + if (((long) secondStep) * ((long) thirdError) < ((long) thirdStep) * ((long) secondError)) { + blockQueue[1] = blockQueue[2].getRelative(secondFace); + blockQueue[0] = blockQueue[1].getRelative(thirdFace); + } else { + blockQueue[1] = blockQueue[2].getRelative(thirdFace); + blockQueue[0] = blockQueue[1].getRelative(secondFace); + } + thirdError -= gridSize; + secondError -= gridSize; + currentBlock = 2; + return; + } else if (secondError > 0) { + blockQueue[1] = blockQueue[0].getRelative(mainFace); + blockQueue[0] = blockQueue[1].getRelative(secondFace); + secondError -= gridSize; + currentBlock = 1; + return; + } else if (thirdError > 0) { + blockQueue[1] = blockQueue[0].getRelative(mainFace); + blockQueue[0] = blockQueue[1].getRelative(thirdFace); + thirdError -= gridSize; + currentBlock = 1; + return; + } else { + blockQueue[0] = blockQueue[0].getRelative(mainFace); + currentBlock = 0; + return; + } + } +} diff --git a/src/main/java/org/bukkit/util/BlockVector.java b/src/main/java/org/bukkit/util/BlockVector.java new file mode 100644 index 00000000..bdf8f6d5 --- /dev/null +++ b/src/main/java/org/bukkit/util/BlockVector.java @@ -0,0 +1,128 @@ +package org.bukkit.util; + +import java.util.Map; +import org.bukkit.configuration.serialization.SerializableAs; + +/** + * A vector with a hash function that floors the X, Y, Z components, a la + * BlockVector in WorldEdit. BlockVectors can be used in hash sets and + * hash maps. Be aware that BlockVectors are mutable, but it is important + * that BlockVectors are never changed once put into a hash set or hash map. + */ +@SerializableAs("BlockVector") +public class BlockVector extends Vector { + + /** + * Construct the vector with all components as 0. + */ + public BlockVector() { + this.x = 0; + this.y = 0; + this.z = 0; + } + + /** + * Construct the vector with another vector. + * + * @param vec The other vector. + */ + public BlockVector(Vector vec) { + this.x = vec.getX(); + this.y = vec.getY(); + this.z = vec.getZ(); + } + + /** + * Construct the vector with provided integer components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided double components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided float components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Checks if another object is equivalent. + * + * @param obj The other object + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlockVector)) { + return false; + } + BlockVector other = (BlockVector) obj; + + return (int) other.getX() == (int) this.x && (int) other.getY() == (int) this.y && (int) other.getZ() == (int) this.z; + + } + + /** + * Returns a hash code for this vector. + * + * @return hash code + */ + @Override + public int hashCode() { + return (Integer.valueOf((int) x).hashCode() >> 13) ^ (Integer.valueOf((int) y).hashCode() >> 7) ^ Integer.valueOf((int) z).hashCode(); + } + + /** + * Get a new block vector. + * + * @return vector + */ + @Override + public BlockVector clone() { + return (BlockVector) super.clone(); + } + + public static BlockVector deserialize(Map args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double) args.get("x"); + } + if (args.containsKey("y")) { + y = (Double) args.get("y"); + } + if (args.containsKey("z")) { + z = (Double) args.get("z"); + } + + return new BlockVector(x, y, z); + } +} diff --git a/src/main/java/org/bukkit/util/CachedServerIcon.java b/src/main/java/org/bukkit/util/CachedServerIcon.java new file mode 100644 index 00000000..6d9aa59b --- /dev/null +++ b/src/main/java/org/bukkit/util/CachedServerIcon.java @@ -0,0 +1,23 @@ +package org.bukkit.util; + +import org.bukkit.Server; +import org.bukkit.event.server.ServerListPingEvent; + +/** + * This is a cached version of a server-icon. It's internal representation + * and implementation is undefined. + * + * @see Server#getServerIcon() + * @see Server#loadServerIcon(java.awt.image.BufferedImage) + * @see Server#loadServerIcon(java.io.File) + * @see ServerListPingEvent#setServerIcon(CachedServerIcon) + */ +public interface CachedServerIcon { + String getData(); // Spigot + + // Paper start + default boolean isEmpty() { + return getData() == null; + } + // Paper end +} diff --git a/src/main/java/org/bukkit/util/ChatPaginator.java b/src/main/java/org/bukkit/util/ChatPaginator.java new file mode 100644 index 00000000..b0b91ad8 --- /dev/null +++ b/src/main/java/org/bukkit/util/ChatPaginator.java @@ -0,0 +1,173 @@ +package org.bukkit.util; + +import org.bukkit.ChatColor; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * The ChatPaginator takes a raw string of arbitrary length and breaks it down + * into an array of strings appropriate for displaying on the Minecraft player + * console. + */ +public class ChatPaginator { + public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 55; // Will never wrap, even with the largest characters + public static final int AVERAGE_CHAT_PAGE_WIDTH = 65; // Will typically not wrap using an average character distribution + public static final int UNBOUNDED_PAGE_WIDTH = Integer.MAX_VALUE; + public static final int OPEN_CHAT_PAGE_HEIGHT = 20; // The height of an expanded chat window + public static final int CLOSED_CHAT_PAGE_HEIGHT = 10; // The height of the default chat window + public static final int UNBOUNDED_PAGE_HEIGHT = Integer.MAX_VALUE; + + /** + * Breaks a raw string up into pages using the default width and height. + * + * @param unpaginatedString The raw string to break. + * @param pageNumber The page number to fetch. + * @return A single chat page. + */ + public static ChatPage paginate(String unpaginatedString, int pageNumber) { + return paginate(unpaginatedString, pageNumber, GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH, CLOSED_CHAT_PAGE_HEIGHT); + } + + /** + * Breaks a raw string up into pages using a provided width and height. + * + * @param unpaginatedString The raw string to break. + * @param pageNumber The page number to fetch. + * @param lineLength The desired width of a chat line. + * @param pageHeight The desired number of lines in a page. + * @return A single chat page. + */ + public static ChatPage paginate(String unpaginatedString, int pageNumber, int lineLength, int pageHeight) { + String[] lines = wordWrap(unpaginatedString, lineLength); + + int totalPages = lines.length / pageHeight + (lines.length % pageHeight == 0 ? 0 : 1); + int actualPageNumber = pageNumber <= totalPages ? pageNumber : totalPages; + + int from = (actualPageNumber - 1) * pageHeight; + int to = from + pageHeight <= lines.length ? from + pageHeight : lines.length; + String[] selectedLines = Arrays.copyOfRange(lines, from, to); + + return new ChatPage(selectedLines, actualPageNumber, totalPages); + } + + /** + * Breaks a raw string up into a series of lines. Words are wrapped using + * spaces as decimeters and the newline character is respected. + * + * @param rawString The raw string to break. + * @param lineLength The length of a line of text. + * @return An array of word-wrapped lines. + */ + public static String[] wordWrap(String rawString, int lineLength) { + // A null string is a single line + if (rawString == null) { + return new String[] {""}; + } + + // A string shorter than the lineWidth is a single line + if (rawString.length() <= lineLength && !rawString.contains("\n")) { + return new String[] {rawString}; + } + + char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination + StringBuilder word = new StringBuilder(); + StringBuilder line = new StringBuilder(); + List lines = new LinkedList(); + int lineColorChars = 0; + + for (int i = 0; i < rawChars.length; i++) { + char c = rawChars[i]; + + // skip chat color modifiers + if (c == ChatColor.COLOR_CHAR) { + word.append(ChatColor.getByChar(rawChars[i + 1])); + lineColorChars += 2; + i++; // Eat the next character as we have already processed it + continue; + } + + if (c == ' ' || c == '\n') { + if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line + for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) { + lines.add(partialWord); + } + } else if (line.length() + 1 + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline + if (line.length() > 0) { + line.append(' '); + } + line.append(word); + lines.add(line.toString()); + line = new StringBuilder(); + lineColorChars = 0; + } else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line + for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) { + lines.add(line.toString()); + line = new StringBuilder(partialWord); + } + lineColorChars = 0; + } else { + if (line.length() > 0) { + line.append(' '); + } + line.append(word); + } + word = new StringBuilder(); + + if (c == '\n') { // Newline forces the line to flush + lines.add(line.toString()); + line = new StringBuilder(); + } + } else { + word.append(c); + } + } + + if (line.length() > 0) { // Only add the last line if there is anything to add + lines.add(line.toString()); + } + + // Iterate over the wrapped lines, applying the last color from one line to the beginning of the next + if (lines.get(0).length() == 0 || lines.get(0).charAt(0) != ChatColor.COLOR_CHAR) { + lines.set(0, ChatColor.WHITE + lines.get(0)); + } + for (int i = 1; i < lines.size(); i++) { + final String pLine = lines.get(i - 1); + final String subLine = lines.get(i); + + char color = pLine.charAt(pLine.lastIndexOf(ChatColor.COLOR_CHAR) + 1); + if (subLine.length() == 0 || subLine.charAt(0) != ChatColor.COLOR_CHAR) { + lines.set(i, ChatColor.getByChar(color) + subLine); + } + } + + return lines.toArray(new String[lines.size()]); + } + + public static class ChatPage { + + private String[] lines; + private int pageNumber; + private int totalPages; + + public ChatPage(String[] lines, int pageNumber, int totalPages) { + this.lines = lines; + this.pageNumber = pageNumber; + this.totalPages = totalPages; + } + + public int getPageNumber() { + return pageNumber; + } + + public int getTotalPages() { + return totalPages; + } + + public String[] getLines() { + + return lines; + } + } +} diff --git a/src/main/java/org/bukkit/util/Consumer.java b/src/main/java/org/bukkit/util/Consumer.java new file mode 100644 index 00000000..fb9e6b90 --- /dev/null +++ b/src/main/java/org/bukkit/util/Consumer.java @@ -0,0 +1,17 @@ +package org.bukkit.util; + +/** + * Represents an operation that accepts a single input argument and returns no + * result. + * + * @param the type of the input to the operation + */ +public interface Consumer { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t); +} diff --git a/src/main/java/org/bukkit/util/EulerAngle.java b/src/main/java/org/bukkit/util/EulerAngle.java new file mode 100644 index 00000000..f5c2773e --- /dev/null +++ b/src/main/java/org/bukkit/util/EulerAngle.java @@ -0,0 +1,147 @@ +package org.bukkit.util; + +/** + * EulerAngle is used to represent 3 angles, one for each + * axis (x, y, z). The angles are in radians + */ +public class EulerAngle { + + /** + * A EulerAngle with every axis set to 0 + */ + public static final EulerAngle ZERO = new EulerAngle(0, 0, 0); + + private final double x; + private final double y; + private final double z; + + /** + * Creates a EularAngle with each axis set to the + * passed angle in radians + * + * @param x the angle for the x axis in radians + * @param y the angle for the y axis in radians + * @param z the angle for the z axis in radians + */ + public EulerAngle(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Returns the angle on the x axis in radians + * + * @return the angle in radians + */ + public double getX() { + return x; + } + + /** + * Returns the angle on the y axis in radians + * + * @return the angle in radians + */ + public double getY() { + return y; + } + + /** + * Returns the angle on the z axis in radians + * + * @return the angle in radians + */ + public double getZ() { + return z; + } + + /** + * Return a EulerAngle which is the result of changing + * the x axis to the passed angle + * + * @param x the angle in radians + * @return the resultant EulerAngle + */ + public EulerAngle setX(double x) { + return new EulerAngle(x, y, z); + } + + /** + * Return a EulerAngle which is the result of changing + * the y axis to the passed angle + * + * @param y the angle in radians + * @return the resultant EulerAngle + */ + public EulerAngle setY(double y) { + return new EulerAngle(x, y, z); + } + + /** + * Return a EulerAngle which is the result of changing + * the z axis to the passed angle + * + * @param z the angle in radians + * @return the resultant EulerAngle + */ + public EulerAngle setZ(double z) { + return new EulerAngle(x, y, z); + } + + /** + * Creates a new EulerAngle which is the result of adding + * the x, y, z components to this EulerAngle + * + * @param x the angle to add to the x axis in radians + * @param y the angle to add to the y axis in radians + * @param z the angle to add to the z axis in radians + * @return the resultant EulerAngle + */ + public EulerAngle add(double x, double y, double z) { + return new EulerAngle( + this.x + x, + this.y + y, + this.z + z + ); + } + + /** + * Creates a new EulerAngle which is the result of subtracting + * the x, y, z components to this EulerAngle + * + * @param x the angle to subtract to the x axis in radians + * @param y the angle to subtract to the y axis in radians + * @param z the angle to subtract to the z axis in radians + * @return the resultant EulerAngle + */ + public EulerAngle subtract(double x, double y, double z) { + return add(-x, -y, -z); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EulerAngle that = (EulerAngle) o; + + return Double.compare(that.x, x) == 0 + && Double.compare(that.y, y) == 0 + && Double.compare(that.z, z) == 0; + + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(x); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } +} diff --git a/src/main/java/org/bukkit/util/FileUtil.java b/src/main/java/org/bukkit/util/FileUtil.java new file mode 100644 index 00000000..7cabf4c5 --- /dev/null +++ b/src/main/java/org/bukkit/util/FileUtil.java @@ -0,0 +1,57 @@ +package org.bukkit.util; + +import java.nio.channels.FileChannel; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Class containing file utilities + */ +public class FileUtil { + + /** + * This method copies one file to another location + * + * @param inFile the source filename + * @param outFile the target filename + * @return true on success + */ + public static boolean copy(File inFile, File outFile) { + if (!inFile.exists()) { + return false; + } + + FileChannel in = null; + FileChannel out = null; + + try { + in = new FileInputStream(inFile).getChannel(); + out = new FileOutputStream(outFile).getChannel(); + + long pos = 0; + long size = in.size(); + + while (pos < size) { + pos += in.transferTo(pos, 10 * 1024 * 1024, out); + } + } catch (IOException ioe) { + return false; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ioe) { + return false; + } + } + + return true; + + } +} diff --git a/src/main/java/org/bukkit/util/NumberConversions.java b/src/main/java/org/bukkit/util/NumberConversions.java new file mode 100644 index 00000000..440d2798 --- /dev/null +++ b/src/main/java/org/bukkit/util/NumberConversions.java @@ -0,0 +1,124 @@ +package org.bukkit.util; + +/** + * Utils for casting number types to other number types + */ +public final class NumberConversions { + private NumberConversions() {} + + public static int floor(double num) { + final int floor = (int) num; + return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63); + } + + public static int ceil(final double num) { + final int floor = (int) num; + return floor == num ? floor : floor + (int) (~Double.doubleToRawLongBits(num) >>> 63); + } + + public static int round(double num) { + return floor(num + 0.5d); + } + + public static double square(double num) { + return num * num; + } + + public static int toInt(Object object) { + if (object instanceof Number) { + return ((Number) object).intValue(); + } + + try { + return Integer.parseInt(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static float toFloat(Object object) { + if (object instanceof Number) { + return ((Number) object).floatValue(); + } + + try { + return Float.parseFloat(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static double toDouble(Object object) { + if (object instanceof Number) { + return ((Number) object).doubleValue(); + } + + try { + return Double.parseDouble(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static long toLong(Object object) { + if (object instanceof Number) { + return ((Number) object).longValue(); + } + + try { + return Long.parseLong(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static short toShort(Object object) { + if (object instanceof Number) { + return ((Number) object).shortValue(); + } + + try { + return Short.parseShort(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static byte toByte(Object object) { + if (object instanceof Number) { + return ((Number) object).byteValue(); + } + + try { + return Byte.parseByte(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static boolean isFinite(double d) { + return Math.abs(d) <= Double.MAX_VALUE; + } + + public static boolean isFinite(float f) { + return Math.abs(f) <= Float.MAX_VALUE; + } + + public static void checkFinite(double d, String message) { + if (!isFinite(d)) { + throw new IllegalArgumentException(message); + } + } + + public static void checkFinite(float d, String message) { + if (!isFinite(d)) { + throw new IllegalArgumentException(message); + } + } +} diff --git a/src/main/java/org/bukkit/util/StringUtil.java b/src/main/java/org/bukkit/util/StringUtil.java new file mode 100644 index 00000000..7ab84f50 --- /dev/null +++ b/src/main/java/org/bukkit/util/StringUtil.java @@ -0,0 +1,58 @@ +package org.bukkit.util; + +import java.util.Collection; +import org.apache.commons.lang3.Validate; + +public class StringUtil { + + /** + * Copies all elements from the iterable collection of originals to the + * collection provided. + * + * @param the collection of strings + * @param token String to search for + * @param originals An iterable collection of strings to filter. + * @param collection The collection to add matches to + * @return the collection provided that would have the elements copied + * into + * @throws UnsupportedOperationException if the collection is immutable + * and originals contains a string which starts with the specified + * search string. + * @throws IllegalArgumentException if any parameter is is null + * @throws IllegalArgumentException if originals contains a null element. + * Note: the collection may be modified before this is thrown + */ + public static > T copyPartialMatches(final String token, final Iterable originals, final T collection) throws UnsupportedOperationException, IllegalArgumentException { + Validate.notNull(token, "Search token cannot be null"); + Validate.notNull(collection, "Collection cannot be null"); + Validate.notNull(originals, "Originals cannot be null"); + + for (String string : originals) { + if (startsWithIgnoreCase(string, token)) { + collection.add(string); + } + } + + return collection; + } + + /** + * This method uses a region to check case-insensitive equality. This + * means the internal array does not need to be copied like a + * toLowerCase() call would. + * + * @param string String to check + * @param prefix Prefix of string to compare + * @return true if provided string starts with, ignoring case, the prefix + * provided + * @throws NullPointerException if prefix is null + * @throws IllegalArgumentException if string is null + */ + public static boolean startsWithIgnoreCase(final String string, final String prefix) throws IllegalArgumentException, NullPointerException { + Validate.notNull(string, "Cannot check a null string for a match"); + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + } +} diff --git a/src/main/java/org/bukkit/util/Vector.java b/src/main/java/org/bukkit/util/Vector.java new file mode 100644 index 00000000..068361f5 --- /dev/null +++ b/src/main/java/org/bukkit/util/Vector.java @@ -0,0 +1,697 @@ +package org.bukkit.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +/** + * Represents a mutable vector. Because the components of Vectors are mutable, + * storing Vectors long term may be dangerous if passing code modifies the + * Vector later. If you want to keep around a Vector, it may be wise to call + * clone() in order to get a copy. + */ +@SerializableAs("Vector") +public class Vector implements Cloneable, ConfigurationSerializable { + private static final long serialVersionUID = -2657651106777219169L; + + private static Random random = new Random(); + + /** + * Threshold for fuzzy equals(). + */ + private static final double epsilon = 0.000001; + + protected double x; + protected double y; + protected double z; + + /** + * Construct the vector with all components as 0. + */ + public Vector() { + this.x = 0; + this.y = 0; + this.z = 0; + } + + /** + * Construct the vector with provided integer components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided double components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided float components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Adds a vector to this one + * + * @param vec The other vector + * @return the same vector + */ + public Vector add(Vector vec) { + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * Subtracts a vector from this one. + * + * @param vec The other vector + * @return the same vector + */ + public Vector subtract(Vector vec) { + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * Multiplies the vector by another. + * + * @param vec The other vector + * @return the same vector + */ + public Vector multiply(Vector vec) { + x *= vec.x; + y *= vec.y; + z *= vec.z; + return this; + } + + /** + * Divides the vector by another. + * + * @param vec The other vector + * @return the same vector + */ + public Vector divide(Vector vec) { + x /= vec.x; + y /= vec.y; + z /= vec.z; + return this; + } + + /** + * Copies another vector + * + * @param vec The other vector + * @return the same vector + */ + public Vector copy(Vector vec) { + x = vec.x; + y = vec.y; + z = vec.z; + return this; + } + + /** + * Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The + * value of this method is not cached and uses a costly square-root + * function, so do not repeatedly call this method to get the vector's + * magnitude. NaN will be returned if the inner result of the sqrt() + * function overflows, which will be caused if the length is too long. + * + * @return the magnitude + */ + public double length() { + return Math.sqrt(NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z)); + } + + /** + * Gets the magnitude of the vector squared. + * + * @return the magnitude + */ + public double lengthSquared() { + return NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z); + } + + /** + * Get the distance between this vector and another. The value of this + * method is not cached and uses a costly square-root function, so do not + * repeatedly call this method to get the vector's magnitude. NaN will be + * returned if the inner result of the sqrt() function overflows, which + * will be caused if the distance is too long. + * + * @param o The other vector + * @return the distance + */ + public double distance(Vector o) { + return Math.sqrt(NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z)); + } + + /** + * Get the squared distance between this vector and another. + * + * @param o The other vector + * @return the distance + */ + public double distanceSquared(Vector o) { + return NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z); + } + + /** + * Gets the angle between this vector and another in radians. + * + * @param other The other vector + * @return angle in radians + */ + public float angle(Vector other) { + double dot = dot(other) / (length() * other.length()); + + return (float) Math.acos(dot); + } + + /** + * Sets this vector to the midpoint between this vector and another. + * + * @param other The other vector + * @return this same vector (now a midpoint) + */ + public Vector midpoint(Vector other) { + x = (x + other.x) / 2; + y = (y + other.y) / 2; + z = (z + other.z) / 2; + return this; + } + + /** + * Gets a new midpoint vector between this vector and another. + * + * @param other The other vector + * @return a new midpoint vector + */ + public Vector getMidpoint(Vector other) { + double x = (this.x + other.x) / 2; + double y = (this.y + other.y) / 2; + double z = (this.z + other.z) / 2; + return new Vector(x, y, z); + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + public Vector multiply(int m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + public Vector multiply(double m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + public Vector multiply(float m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Calculates the dot product of this vector with another. The dot product + * is defined as x1*x2+y1*y2+z1*z2. The returned value is a scalar. + * + * @param other The other vector + * @return dot product + */ + public double dot(Vector other) { + return x * other.x + y * other.y + z * other.z; + } + + /** + * Calculates the cross product of this vector with another. The cross + * product is defined as: + *

      + *
    • x = y1 * z2 - y2 * z1 + *
    • y = z1 * x2 - z2 * x1 + *
    • z = x1 * y2 - x2 * y1 + *
    + * + * @param o The other vector + * @return the same vector + */ + public Vector crossProduct(Vector o) { + double newX = y * o.z - o.y * z; + double newY = z * o.x - o.z * x; + double newZ = x * o.y - o.x * y; + + x = newX; + y = newY; + z = newZ; + return this; + } + + /** + * Calculates the cross product of this vector with another without mutating + * the original. The cross product is defined as: + *
      + *
    • x = y1 * z2 - y2 * z1 + *
    • y = z1 * x2 - z2 * x1 + *
    • z = x1 * y2 - x2 * y1 + *
    + * + * @param o The other vector + * @return a new vector + */ + public Vector getCrossProduct(Vector o) { + double x = this.y * o.z - o.y * this.z; + double y = this.z * o.x - o.z * this.x; + double z = this.x * o.y - o.x * this.y; + return new Vector(x, y, z); + } + + /** + * Converts this vector to a unit vector (a vector with length of 1). + * + * @return the same vector + */ + public Vector normalize() { + double length = length(); + + x /= length; + y /= length; + z /= length; + + return this; + } + + /** + * Zero this vector's components. + * + * @return the same vector + */ + public Vector zero() { + x = 0; + y = 0; + z = 0; + return this; + } + + /** + * Returns whether this vector is in an axis-aligned bounding box. + *

    + * The minimum and maximum vectors given must be truly the minimum and + * maximum X, Y and Z components. + * + * @param min Minimum vector + * @param max Maximum vector + * @return whether this vector is in the AABB + */ + public boolean isInAABB(Vector min, Vector max) { + return x >= min.x && x <= max.x && y >= min.y && y <= max.y && z >= min.z && z <= max.z; + } + + /** + * Returns whether this vector is within a sphere. + * + * @param origin Sphere origin. + * @param radius Sphere radius + * @return whether this vector is in the sphere + */ + public boolean isInSphere(Vector origin, double radius) { + return (NumberConversions.square(origin.x - x) + NumberConversions.square(origin.y - y) + NumberConversions.square(origin.z - z)) <= NumberConversions.square(radius); + } + + /** + * Gets the X component. + * + * @return The X component. + */ + public double getX() { + return x; + } + + /** + * Gets the floored value of the X component, indicating the block that + * this vector is contained with. + * + * @return block X + */ + public int getBlockX() { + return NumberConversions.floor(x); + } + + /** + * Gets the Y component. + * + * @return The Y component. + */ + public double getY() { + return y; + } + + /** + * Gets the floored value of the Y component, indicating the block that + * this vector is contained with. + * + * @return block y + */ + public int getBlockY() { + return NumberConversions.floor(y); + } + + /** + * Gets the Z component. + * + * @return The Z component. + */ + public double getZ() { + return z; + } + + /** + * Gets the floored value of the Z component, indicating the block that + * this vector is contained with. + * + * @return block z + */ + public int getBlockZ() { + return NumberConversions.floor(z); + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + public Vector setX(int x) { + this.x = x; + return this; + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + public Vector setX(double x) { + this.x = x; + return this; + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + public Vector setX(float x) { + this.x = x; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + public Vector setY(int y) { + this.y = y; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + public Vector setY(double y) { + this.y = y; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + public Vector setY(float y) { + this.y = y; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + public Vector setZ(int z) { + this.z = z; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + public Vector setZ(double z) { + this.z = z; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + public Vector setZ(float z) { + this.z = z; + return this; + } + + /** + * Checks to see if two objects are equal. + *

    + * Only two Vectors can ever return true. This method uses a fuzzy match + * to account for floating point errors. The epsilon can be retrieved + * with epsilon. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector)) { + return false; + } + + Vector other = (Vector) obj; + + return Math.abs(x - other.x) < epsilon && Math.abs(y - other.y) < epsilon && Math.abs(z - other.z) < epsilon && (this.getClass().equals(obj.getClass())); + } + + /** + * Returns a hash code for this vector + * + * @return hash code + */ + @Override + public int hashCode() { + int hash = 7; + + hash = 79 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); + hash = 79 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); + hash = 79 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); + return hash; + } + + /** + * Get a new vector. + * + * @return vector + */ + @Override + public Vector clone() { + try { + return (Vector) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + /** + * Returns this vector's components as x,y,z. + */ + @Override + public String toString() { + return x + "," + y + "," + z; + } + + /** + * Gets a Location version of this vector with yaw and pitch being 0. + * + * @param world The world to link the location to. + * @return the location + */ + public Location toLocation(World world) { + return new Location(world, x, y, z); + } + + /** + * Gets a Location version of this vector. + * + * @param world The world to link the location to. + * @param yaw The desired yaw. + * @param pitch The desired pitch. + * @return the location + */ + public Location toLocation(World world, float yaw, float pitch) { + return new Location(world, x, y, z, yaw, pitch); + } + + /** + * Get the block vector of this vector. + * + * @return A block vector. + */ + public BlockVector toBlockVector() { + return new BlockVector(x, y, z); + } + + /** + * Check if each component of this Vector is finite. + * + * @throws IllegalArgumentException if any component is not finite + */ + public void checkFinite() throws IllegalArgumentException { + NumberConversions.checkFinite(x, "x not finite"); + NumberConversions.checkFinite(y, "y not finite"); + NumberConversions.checkFinite(z, "z not finite"); + } + + /** + * Get the threshold used for equals(). + * + * @return The epsilon. + */ + public static double getEpsilon() { + return epsilon; + } + + /** + * Gets the minimum components of two vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @return minimum + */ + public static Vector getMinimum(Vector v1, Vector v2) { + return new Vector(Math.min(v1.x, v2.x), Math.min(v1.y, v2.y), Math.min(v1.z, v2.z)); + } + + /** + * Gets the maximum components of two vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @return maximum + */ + public static Vector getMaximum(Vector v1, Vector v2) { + return new Vector(Math.max(v1.x, v2.x), Math.max(v1.y, v2.y), Math.max(v1.z, v2.z)); + } + + /** + * Gets a random vector with components having a random value between 0 + * and 1. + * + * @return A random vector. + */ + public static Vector getRandom() { + return new Vector(random.nextDouble(), random.nextDouble(), random.nextDouble()); + } + + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("x", getX()); + result.put("y", getY()); + result.put("z", getZ()); + + return result; + } + + public static Vector deserialize(Map args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double) args.get("x"); + } + if (args.containsKey("y")) { + y = (Double) args.get("y"); + } + if (args.containsKey("z")) { + z = (Double) args.get("z"); + } + + return new Vector(x, y, z); + } +} diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java new file mode 100644 index 00000000..13725531 --- /dev/null +++ b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java @@ -0,0 +1,62 @@ +package org.bukkit.util.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +/** + * This class is designed to be used in conjunction with the {@link + * ConfigurationSerializable} API. It translates objects back to their + * original implementation after being serialized by {@link + * BukkitObjectInputStream}. + *

    + * Behavior of implementations extending this class is not guaranteed across + * future versions. + */ +public class BukkitObjectInputStream extends ObjectInputStream { + + /** + * Constructor provided to mirror super functionality. + * + * @throws IOException if an I/O error occurs while reading stream heade + * @see ObjectInputStream#ObjectInputStream() + */ + protected BukkitObjectInputStream() throws IOException, SecurityException { + super(); + super.enableResolveObject(true); + } + + /** + * Object input stream decoration constructor. + * + * @param in the input stream to wrap + * @throws IOException if an I/O error occurs while reading stream header + * @see ObjectInputStream#ObjectInputStream(InputStream) + */ + public BukkitObjectInputStream(InputStream in) throws IOException { + super(in); + super.enableResolveObject(true); + } + + @Override + protected Object resolveObject(Object obj) throws IOException { + if (obj instanceof Wrapper) { + try { + (obj = ConfigurationSerialization.deserializeObject(((Wrapper) obj).map)).getClass(); // NPE + } catch (Throwable ex) { + throw newIOException("Failed to deserialize object", ex); + } + } + + return super.resolveObject(obj); + } + + private static IOException newIOException(String string, Throwable cause) { + IOException exception = new IOException(string); + exception.initCause(cause); + return exception; + } +} diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java new file mode 100644 index 00000000..84528502 --- /dev/null +++ b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java @@ -0,0 +1,52 @@ +package org.bukkit.util.io; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +/** + * This class is designed to be used in conjunction with the {@link + * ConfigurationSerializable} API. It translates objects to an internal + * implementation for later deserialization using {@link + * BukkitObjectInputStream}. + *

    + * Behavior of implementations extending this class is not guaranteed across + * future versions. + */ +public class BukkitObjectOutputStream extends ObjectOutputStream { + + /** + * Constructor provided to mirror super functionality. + * + * @throws IOException if an I/O error occurs while writing stream header + * @see ObjectOutputStream#ObjectOutputStream() + */ + protected BukkitObjectOutputStream() throws IOException, SecurityException { + super(); + super.enableReplaceObject(true); + } + + /** + * Object output stream decoration constructor. + * + * @param out the stream to wrap + * @throws IOException if an I/O error occurs while writing stream header + * @see ObjectOutputStream#ObjectOutputStream(OutputStream) + */ + public BukkitObjectOutputStream(OutputStream out) throws IOException { + super(out); + super.enableReplaceObject(true); + } + + @Override + protected Object replaceObject(Object obj) throws IOException { + if (!(obj instanceof Serializable) && (obj instanceof ConfigurationSerializable)) { + obj = Wrapper.newWrapper((ConfigurationSerializable) obj); + } + + return super.replaceObject(obj); + } +} diff --git a/src/main/java/org/bukkit/util/io/Wrapper.java b/src/main/java/org/bukkit/util/io/Wrapper.java new file mode 100644 index 00000000..e45605b3 --- /dev/null +++ b/src/main/java/org/bukkit/util/io/Wrapper.java @@ -0,0 +1,23 @@ +package org.bukkit.util.io; + +import java.io.Serializable; +import java.util.Map; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import com.google.common.collect.ImmutableMap; + +class Wrapper & Serializable> implements Serializable { + private static final long serialVersionUID = -986209235411767547L; + + final T map; + + static Wrapper> newWrapper(ConfigurationSerializable obj) { + return new Wrapper>(ImmutableMap.builder().put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(obj.getClass())).putAll(obj.serialize()).build()); + } + + private Wrapper(T map) { + this.map = map; + } +} diff --git a/src/main/java/org/bukkit/util/noise/NoiseGenerator.java b/src/main/java/org/bukkit/util/noise/NoiseGenerator.java new file mode 100644 index 00000000..72c92f33 --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/NoiseGenerator.java @@ -0,0 +1,176 @@ +package org.bukkit.util.noise; + +/** + * Base class for all noise generators + */ +public abstract class NoiseGenerator { + protected final int perm[] = new int[512]; + protected double offsetX; + protected double offsetY; + protected double offsetZ; + + /** + * Speedy floor, faster than (int)Math.floor(x) + * + * @param x Value to floor + * @return Floored value + */ + public static int floor(double x) { + return x >= 0 ? (int) x : (int) x - 1; + } + + protected static double fade(double x) { + return x * x * x * (x * (x * 6 - 15) + 10); + } + + protected static double lerp(double x, double y, double z) { + return y + x * (z - y); + } + + protected static double grad(int hash, double x, double y, double z) { + hash &= 15; + double u = hash < 8 ? x : y; + double v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z; + return ((hash & 1) == 0 ? u : -u) + ((hash & 2) == 0 ? v : -v); + } + + /** + * Computes and returns the 1D noise for the given coordinate in 1D space + * + * @param x X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x) { + return noise(x, 0, 0); + } + + /** + * Computes and returns the 2D noise for the given coordinates in 2D space + * + * @param x X coordinate + * @param y Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x, double y) { + return noise(x, y, 0); + } + + /** + * Computes and returns the 3D noise for the given coordinates in 3D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public abstract double noise(double x, double y, double z); + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, int octaves, double frequency, double amplitude) { + return noise(x, 0, 0, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, int octaves, double frequency, double amplitude, boolean normalized) { + return noise(x, 0, 0, octaves, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, int octaves, double frequency, double amplitude) { + return noise(x, y, 0, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, int octaves, double frequency, double amplitude, boolean normalized) { + return noise(x, y, 0, octaves, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, int octaves, double frequency, double amplitude) { + return noise(x, y, z, octaves, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, int octaves, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + for (int i = 0; i < octaves; i++) { + result += noise(x * freq, y * freq, z * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/util/noise/OctaveGenerator.java b/src/main/java/org/bukkit/util/noise/OctaveGenerator.java new file mode 100644 index 00000000..a87304ba --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/OctaveGenerator.java @@ -0,0 +1,199 @@ +package org.bukkit.util.noise; + +/** + * Creates noise using unbiased octaves + */ +public abstract class OctaveGenerator { + protected final NoiseGenerator[] octaves; + protected double xScale = 1; + protected double yScale = 1; + protected double zScale = 1; + + protected OctaveGenerator(NoiseGenerator[] octaves) { + this.octaves = octaves; + } + + /** + * Sets the scale used for all coordinates passed to this generator. + *

    + * This is the equivalent to setting each coordinate to the specified + * value. + * + * @param scale New value to scale each coordinate by + */ + public void setScale(double scale) { + setXScale(scale); + setYScale(scale); + setZScale(scale); + } + + /** + * Gets the scale used for each X-coordinates passed + * + * @return X scale + */ + public double getXScale() { + return xScale; + } + + /** + * Sets the scale used for each X-coordinates passed + * + * @param scale New X scale + */ + public void setXScale(double scale) { + xScale = scale; + } + + /** + * Gets the scale used for each Y-coordinates passed + * + * @return Y scale + */ + public double getYScale() { + return yScale; + } + + /** + * Sets the scale used for each Y-coordinates passed + * + * @param scale New Y scale + */ + public void setYScale(double scale) { + yScale = scale; + } + + /** + * Gets the scale used for each Z-coordinates passed + * + * @return Z scale + */ + public double getZScale() { + return zScale; + } + + /** + * Sets the scale used for each Z-coordinates passed + * + * @param scale New Z scale + */ + public void setZScale(double scale) { + zScale = scale; + } + + /** + * Gets a clone of the individual octaves used within this generator + * + * @return Clone of the individual octaves + */ + public NoiseGenerator[] getOctaves() { + return octaves.clone(); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double frequency, double amplitude) { + return noise(x, 0, 0, frequency, amplitude); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double frequency, double amplitude, boolean normalized) { + return noise(x, 0, 0, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double frequency, double amplitude) { + return noise(x, y, 0, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double frequency, double amplitude, boolean normalized) { + return noise(x, y, 0, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, double frequency, double amplitude) { + return noise(x, y, z, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + x *= xScale; + y *= yScale; + z *= zScale; + + for (NoiseGenerator octave : octaves) { + result += octave.noise(x * freq, y * freq, z * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java b/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java new file mode 100644 index 00000000..858c3f13 --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java @@ -0,0 +1,217 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; + +/** + * Generates noise using the "classic" perlin generator + * + * @see SimplexNoiseGenerator "Improved" and faster version with slightly + * different results + */ +public class PerlinNoiseGenerator extends NoiseGenerator { + protected static final int grad3[][] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, + {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, + {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}}; + private static final PerlinNoiseGenerator instance = new PerlinNoiseGenerator(); + + protected PerlinNoiseGenerator() { + int p[] = {151, 160, 137, 91, 90, 15, 131, 13, 201, + 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, + 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, + 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, + 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, + 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, + 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, + 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, + 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, + 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, + 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, + 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, + 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, + 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, + 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, + 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180}; + + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + } + } + + /** + * Creates a seeded perlin noise generator for the given world + * + * @param world World to construct this generator for + */ + public PerlinNoiseGenerator(World world) { + this(new Random(world.getSeed())); + } + + /** + * Creates a seeded perlin noise generator for the given seed + * + * @param seed Seed to construct this generator for + */ + public PerlinNoiseGenerator(long seed) { + this(new Random(seed)); + } + + /** + * Creates a seeded perlin noise generator with the given Random + * + * @param rand Random to construct with + */ + public PerlinNoiseGenerator(Random rand) { + offsetX = rand.nextDouble() * 256; + offsetY = rand.nextDouble() * 256; + offsetZ = rand.nextDouble() * 256; + + for (int i = 0; i < 256; i++) { + perm[i] = rand.nextInt(256); + } + + for (int i = 0; i < 256; i++) { + int pos = rand.nextInt(256 - i) + i; + int old = perm[i]; + + perm[i] = perm[pos]; + perm[pos] = old; + perm[i + 256] = perm[i]; + } + } + + /** + * Computes and returns the 1D unseeded perlin noise for the given + * coordinates in 1D space + * + * @param x X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x) { + return instance.noise(x); + } + + /** + * Computes and returns the 2D unseeded perlin noise for the given + * coordinates in 2D space + * + * @param x X coordinate + * @param y Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y) { + return instance.noise(x, y); + } + + /** + * Computes and returns the 3D unseeded perlin noise for the given + * coordinates in 3D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y, double z) { + return instance.noise(x, y, z); + } + + /** + * Gets the singleton unseeded instance of this generator + * + * @return Singleton + */ + public static PerlinNoiseGenerator getInstance() { + return instance; + } + + @Override + public double noise(double x, double y, double z) { + x += offsetX; + y += offsetY; + z += offsetZ; + + int floorX = floor(x); + int floorY = floor(y); + int floorZ = floor(z); + + // Find unit cube containing the point + int X = floorX & 255; + int Y = floorY & 255; + int Z = floorZ & 255; + + // Get relative xyz coordinates of the point within the cube + x -= floorX; + y -= floorY; + z -= floorZ; + + // Compute fade curves for xyz + double fX = fade(x); + double fY = fade(y); + double fZ = fade(z); + + // Hash coordinates of the cube corners + int A = perm[X] + Y; + int AA = perm[A] + Z; + int AB = perm[A + 1] + Z; + int B = perm[X + 1] + Y; + int BA = perm[B] + Z; + int BB = perm[B + 1] + Z; + + return lerp(fZ, lerp(fY, lerp(fX, grad(perm[AA], x, y, z), + grad(perm[BA], x - 1, y, z)), + lerp(fX, grad(perm[AB], x, y - 1, z), + grad(perm[BB], x - 1, y - 1, z))), + lerp(fY, lerp(fX, grad(perm[AA + 1], x, y, z - 1), + grad(perm[BA + 1], x - 1, y, z - 1)), + lerp(fX, grad(perm[AB + 1], x, y - 1, z - 1), + grad(perm[BB + 1], x - 1, y - 1, z - 1)))); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, int octaves, double frequency, double amplitude) { + return instance.noise(x, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, double y, int octaves, double frequency, double amplitude) { + return instance.noise(x, y, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, double y, double z, int octaves, double frequency, double amplitude) { + return instance.noise(x, y, z, octaves, frequency, amplitude); + } +} diff --git a/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java b/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java new file mode 100644 index 00000000..55b7ad7e --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java @@ -0,0 +1,50 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; + +/** + * Creates perlin noise through unbiased octaves + */ +public class PerlinOctaveGenerator extends OctaveGenerator { + + /** + * Creates a perlin octave generator for the given world + * + * @param world World to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(World world, int octaves) { + this(new Random(world.getSeed()), octaves); + } + + /** + * Creates a perlin octave generator for the given world + * + * @param seed Seed to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(long seed, int octaves) { + this(new Random(seed), octaves); + } + + /** + * Creates a perlin octave generator for the given {@link Random} + * + * @param rand Random object to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(Random rand, int octaves) { + super(createOctaves(rand, octaves)); + } + + private static NoiseGenerator[] createOctaves(Random rand, int octaves) { + NoiseGenerator[] result = new NoiseGenerator[octaves]; + + for (int i = 0; i < octaves; i++) { + result[i] = new PerlinNoiseGenerator(rand); + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java b/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java new file mode 100644 index 00000000..7dac89b0 --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java @@ -0,0 +1,520 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; + +/** + * Generates simplex-based noise. + *

    + * This is a modified version of the freely published version in the paper by + * Stefan Gustavson at + * + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + */ +public class SimplexNoiseGenerator extends PerlinNoiseGenerator { + protected static final double SQRT_3 = Math.sqrt(3); + protected static final double SQRT_5 = Math.sqrt(5); + protected static final double F2 = 0.5 * (SQRT_3 - 1); + protected static final double G2 = (3 - SQRT_3) / 6; + protected static final double G22 = G2 * 2.0 - 1; + protected static final double F3 = 1.0 / 3.0; + protected static final double G3 = 1.0 / 6.0; + protected static final double F4 = (SQRT_5 - 1.0) / 4.0; + protected static final double G4 = (5.0 - SQRT_5) / 20.0; + protected static final double G42 = G4 * 2.0; + protected static final double G43 = G4 * 3.0; + protected static final double G44 = G4 * 4.0 - 1.0; + protected static final int grad4[][] = {{0, 1, 1, 1}, {0, 1, 1, -1}, {0, 1, -1, 1}, {0, 1, -1, -1}, + {0, -1, 1, 1}, {0, -1, 1, -1}, {0, -1, -1, 1}, {0, -1, -1, -1}, + {1, 0, 1, 1}, {1, 0, 1, -1}, {1, 0, -1, 1}, {1, 0, -1, -1}, + {-1, 0, 1, 1}, {-1, 0, 1, -1}, {-1, 0, -1, 1}, {-1, 0, -1, -1}, + {1, 1, 0, 1}, {1, 1, 0, -1}, {1, -1, 0, 1}, {1, -1, 0, -1}, + {-1, 1, 0, 1}, {-1, 1, 0, -1}, {-1, -1, 0, 1}, {-1, -1, 0, -1}, + {1, 1, 1, 0}, {1, 1, -1, 0}, {1, -1, 1, 0}, {1, -1, -1, 0}, + {-1, 1, 1, 0}, {-1, 1, -1, 0}, {-1, -1, 1, 0}, {-1, -1, -1, 0}}; + protected static final int simplex[][] = { + {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 0, 0, 0}, {0, 2, 3, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 2, 3, 0}, + {0, 2, 1, 3}, {0, 0, 0, 0}, {0, 3, 1, 2}, {0, 3, 2, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 3, 2, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {1, 2, 0, 3}, {0, 0, 0, 0}, {1, 3, 0, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, + {1, 0, 2, 3}, {1, 0, 3, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 0, 3, 1}, {0, 0, 0, 0}, {2, 1, 3, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {2, 0, 1, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 2, 0}, + {2, 1, 0, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 1, 0, 2}, {0, 0, 0, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}; + protected double offsetW; + private static final SimplexNoiseGenerator instance = new SimplexNoiseGenerator(); + + protected SimplexNoiseGenerator() { + super(); + } + + /** + * Creates a seeded simplex noise generator for the given world + * + * @param world World to construct this generator for + */ + public SimplexNoiseGenerator(World world) { + this(new Random(world.getSeed())); + } + + /** + * Creates a seeded simplex noise generator for the given seed + * + * @param seed Seed to construct this generator for + */ + public SimplexNoiseGenerator(long seed) { + this(new Random(seed)); + } + + /** + * Creates a seeded simplex noise generator with the given Random + * + * @param rand Random to construct with + */ + public SimplexNoiseGenerator(Random rand) { + super(rand); + offsetW = rand.nextDouble() * 256; + } + + protected static double dot(int g[], double x, double y) { + return g[0] * x + g[1] * y; + } + + protected static double dot(int g[], double x, double y, double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + protected static double dot(int g[], double x, double y, double z, double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + /** + * Computes and returns the 1D unseeded simplex noise for the given + * coordinates in 1D space + * + * @param xin X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin) { + return instance.noise(xin); + } + + /** + * Computes and returns the 2D unseeded simplex noise for the given + * coordinates in 2D space + * + * @param xin X coordinate + * @param yin Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin, double yin) { + return instance.noise(xin, yin); + } + + /** + * Computes and returns the 3D unseeded simplex noise for the given + * coordinates in 3D space + * + * @param xin X coordinate + * @param yin Y coordinate + * @param zin Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin, double yin, double zin) { + return instance.noise(xin, yin, zin); + } + + /** + * Computes and returns the 4D simplex noise for the given coordinates in + * 4D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param w W coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y, double z, double w) { + return instance.noise(x, y, z, w); + } + + @Override + public double noise(double xin, double yin, double zin) { + xin += offsetX; + yin += offsetY; + zin += offsetZ; + + double n0, n1, n2, n3; // Noise contributions from the four corners + + // Skew the input space to determine which simplex cell we're in + double s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D + int i = floor(xin + s); + int j = floor(yin + s); + int k = floor(zin + s); + double t = (i + j + k) * G3; + double X0 = i - t; // Unskew the cell origin back to (x,y,z) space + double Y0 = j - t; + double Z0 = k - t; + double x0 = xin - X0; // The x,y,z distances from the cell origin + double y0 = yin - Y0; + double z0 = zin - Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0 y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 + G22; // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 + G22; + + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + int gi0 = perm[ii + perm[jj]] % 12; + int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + /** + * Computes and returns the 4D simplex noise for the given coordinates in + * 4D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param w W coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x, double y, double z, double w) { + x += offsetX; + y += offsetY; + z += offsetZ; + w += offsetW; + + double n0, n1, n2, n3, n4; // Noise contributions from the five corners + + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in + double s = (x + y + z + w) * F4; // Factor for 4D skewing + int i = floor(x + s); + int j = floor(y + s); + int k = floor(z + s); + int l = floor(w + s); + + double t = (i + j + k + l) * G4; // Factor for 4D unskewing + double X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + double Y0 = j - t; + double Z0 = k - t; + double W0 = l - t; + double x0 = x - X0; // The x,y,z,w distances from the cell origin + double y0 = y - Y0; + double z0 = z - Z0; + double w0 = w - W0; + + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex we’re in. + // First, six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to add up binary bits + // for an integer index. + int c1 = (x0 > y0) ? 32 : 0; + int c2 = (x0 > z0) ? 16 : 0; + int c3 = (y0 > z0) ? 8 : 0; + int c4 = (x0 > w0) ? 4 : 0; + int c5 = (y0 > w0) ? 2 : 0; + int c6 = (z0 > w0) ? 1 : 0; + int c = c1 + c2 + c3 + c4 + c5 + c6; + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = simplex[c][1] >= 3 ? 1 : 0; + k1 = simplex[c][2] >= 3 ? 1 : 0; + l1 = simplex[c][3] >= 3 ? 1 : 0; + + // The number 2 in the "simplex" array is at the second largest coordinate. + i2 = simplex[c][0] >= 2 ? 1 : 0; + j2 = simplex[c][1] >= 2 ? 1 : 0; + k2 = simplex[c][2] >= 2 ? 1 : 0; + l2 = simplex[c][3] >= 2 ? 1 : 0; + + // The number 1 in the "simplex" array is at the second smallest coordinate. + i3 = simplex[c][0] >= 1 ? 1 : 0; + j3 = simplex[c][1] >= 1 ? 1 : 0; + k3 = simplex[c][2] >= 1 ? 1 : 0; + l3 = simplex[c][3] >= 1 ? 1 : 0; + + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + + double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + + double x2 = x0 - i2 + G42; // Offsets for third corner in (x,y,z,w) coords + double y2 = y0 - j2 + G42; + double z2 = z0 - k2 + G42; + double w2 = w0 - l2 + G42; + + double x3 = x0 - i3 + G43; // Offsets for fourth corner in (x,y,z,w) coords + double y3 = y0 - j3 + G43; + double z3 = z0 - k3 + G43; + double w3 = w0 - l3 + G43; + + double x4 = x0 + G44; // Offsets for last corner in (x,y,z,w) coords + double y4 = y0 + G44; + double z4 = z0 + G44; + double w4 = w0 + G44; + + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 255; + int jj = j & 255; + int kk = k & 255; + int ll = l & 255; + + int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0) { + n4 = 0.0; + } else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } + + /** + * Gets the singleton unseeded instance of this generator + * + * @return Singleton + */ + public static SimplexNoiseGenerator getInstance() { + return instance; + } +} diff --git a/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java b/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java new file mode 100644 index 00000000..61e66aa2 --- /dev/null +++ b/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java @@ -0,0 +1,129 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; + +/** + * Creates simplex noise through unbiased octaves + */ +public class SimplexOctaveGenerator extends OctaveGenerator { + private double wScale = 1; + + /** + * Creates a simplex octave generator for the given world + * + * @param world World to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(World world, int octaves) { + this(new Random(world.getSeed()), octaves); + } + + /** + * Creates a simplex octave generator for the given world + * + * @param seed Seed to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(long seed, int octaves) { + this(new Random(seed), octaves); + } + + /** + * Creates a simplex octave generator for the given {@link Random} + * + * @param rand Random object to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(Random rand, int octaves) { + super(createOctaves(rand, octaves)); + } + + @Override + public void setScale(double scale) { + super.setScale(scale); + setWScale(scale); + } + + /** + * Gets the scale used for each W-coordinates passed + * + * @return W scale + */ + public double getWScale() { + return wScale; + } + + /** + * Sets the scale used for each W-coordinates passed + * + * @param scale New W scale + */ + public void setWScale(double scale) { + wScale = scale; + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param w W-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, double w, double frequency, double amplitude) { + return noise(x, y, z, w, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param w W-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, double w, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + x *= xScale; + y *= yScale; + z *= zScale; + w *= wScale; + + for (NoiseGenerator octave : octaves) { + result += ((SimplexNoiseGenerator) octave).noise(x * freq, y * freq, z * freq, w * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } + + private static NoiseGenerator[] createOctaves(Random rand, int octaves) { + NoiseGenerator[] result = new NoiseGenerator[octaves]; + + for (int i = 0; i < octaves; i++) { + result[i] = new SimplexNoiseGenerator(rand); + } + + return result; + } +} diff --git a/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java b/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java new file mode 100644 index 00000000..092370e9 --- /dev/null +++ b/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java @@ -0,0 +1,22 @@ +package org.bukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; + +public final class BroadcastPermissions { + private static final String ROOT = "bukkit.broadcast"; + private static final String PREFIX = ROOT + "."; + + private BroadcastPermissions() {} + + public static Permission registerPermissions(Permission parent) { + Permission broadcasts = DefaultPermissions.registerPermission(ROOT, "Allows the user to receive all broadcast messages", parent); + + DefaultPermissions.registerPermission(PREFIX + "admin", "Allows the user to receive administrative broadcasts", PermissionDefault.OP, broadcasts); + DefaultPermissions.registerPermission(PREFIX + "user", "Allows the user to receive user broadcasts", PermissionDefault.TRUE, broadcasts); + + broadcasts.recalculatePermissibles(); + + return broadcasts; + } +} diff --git a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java new file mode 100644 index 00000000..619646ee --- /dev/null +++ b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java @@ -0,0 +1,23 @@ +package org.bukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; + +public final class CommandPermissions { + private static final String ROOT = "bukkit.command"; + private static final String PREFIX = ROOT + "."; + + private CommandPermissions() {} + + public static Permission registerPermissions(Permission parent) { + Permission commands = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all CraftBukkit commands", parent); + + DefaultPermissions.registerPermission(PREFIX + "help", "Allows the user to view the vanilla help menu", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "plugins", "Allows the user to view the list of plugins running on this server", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload the server settings", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "version", "Allows the user to view the version of the server", PermissionDefault.TRUE, commands); + + commands.recalculatePermissibles(); + return commands; + } +} diff --git a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java new file mode 100644 index 00000000..8c0df8e4 --- /dev/null +++ b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java @@ -0,0 +1,82 @@ +package org.bukkit.util.permissions; + +import java.util.Map; +import org.bukkit.Bukkit; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; + +public final class DefaultPermissions { + private static final String ROOT = "craftbukkit"; + private static final String LEGACY_PREFIX = "craft"; + + private DefaultPermissions() {} + + public static Permission registerPermission(Permission perm) { + return registerPermission(perm, true); + } + + public static Permission registerPermission(Permission perm, boolean withLegacy) { + Permission result = perm; + + try { + Bukkit.getPluginManager().addPermission(perm); + } catch (IllegalArgumentException ex) { + result = Bukkit.getPluginManager().getPermission(perm.getName()); + } + + if (withLegacy) { + Permission legacy = new Permission(LEGACY_PREFIX + result.getName(), result.getDescription(), PermissionDefault.FALSE); + legacy.getChildren().put(result.getName(), true); + registerPermission(perm, false); + } + + return result; + } + + public static Permission registerPermission(Permission perm, Permission parent) { + parent.getChildren().put(perm.getName(), true); + return registerPermission(perm); + } + + public static Permission registerPermission(String name, String desc) { + Permission perm = registerPermission(new Permission(name, desc)); + return perm; + } + + public static Permission registerPermission(String name, String desc, Permission parent) { + Permission perm = registerPermission(name, desc); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + public static Permission registerPermission(String name, String desc, PermissionDefault def) { + Permission perm = registerPermission(new Permission(name, desc, def)); + return perm; + } + + public static Permission registerPermission(String name, String desc, PermissionDefault def, Permission parent) { + Permission perm = registerPermission(name, desc, def); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + public static Permission registerPermission(String name, String desc, PermissionDefault def, Map children) { + Permission perm = registerPermission(new Permission(name, desc, def, children)); + return perm; + } + + public static Permission registerPermission(String name, String desc, PermissionDefault def, Map children, Permission parent) { + Permission perm = registerPermission(name, desc, def, children); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + public static void registerCorePermissions() { + Permission parent = registerPermission(ROOT, "Gives the user the ability to use all CraftBukkit utilities and commands"); + + CommandPermissions.registerPermissions(parent); + BroadcastPermissions.registerPermissions(parent); + + parent.recalculatePermissibles(); + } +} diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java new file mode 100644 index 00000000..f16d1313 --- /dev/null +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -0,0 +1,286 @@ +package org.spigotmc; + +import net.minecraft.entity.*; +import net.minecraft.entity.boss.EntityWither; +import net.minecraft.entity.effect.EntityWeatherEffect; +import net.minecraft.entity.item.EntityEnderCrystal; +import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.entity.item.EntityFireworkRocket; +import net.minecraft.entity.item.EntityTNTPrimed; +import net.minecraft.entity.monster.EntityCreeper; +import net.minecraft.entity.monster.EntityMob; +import net.minecraft.entity.monster.EntitySlime; +import net.minecraft.entity.passive.EntityAmbientCreature; +import net.minecraft.entity.passive.EntityAnimal; +import net.minecraft.entity.passive.EntitySheep; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityFireball; +import net.minecraft.entity.projectile.EntityThrowable; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.common.DimensionManager; + +public class ActivationRange +{ + static AxisAlignedBB maxBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB miscBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB animalBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB monsterBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + + /** + * Initializes an entities type on construction to specify what group this + * entity is in for activation ranges. + * + * @param entity + * @return group id + */ + public static byte initializeEntityActivationType(Entity entity) + { + // Cauldron start - account for entities that dont extend EntityMob, EntityAmbientCreature, EntityCreature + if ( entity instanceof EntityMob || entity instanceof EntitySlime || entity.isCreatureType(EnumCreatureType.MONSTER, false)) // Cauldron - account for entities that dont extend EntityMob + { + return 1; // Monster + } else if ( entity instanceof EntityCreature || entity instanceof EntityAmbientCreature || entity.isCreatureType(EnumCreatureType.CREATURE, false) + || entity.isCreatureType(EnumCreatureType.WATER_CREATURE, false) || entity.isCreatureType(EnumCreatureType.AMBIENT, false)) + { + return 2; // Animal + // Cauldron end + } else + { + return 3; // Misc + } + } + + /** + * These entities are excluded from Activation range checks. + * + * @param entity + * @return boolean If it should always tick. + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { + if(config == null && DimensionManager.getWorld(0) != null){ + config = DimensionManager.getWorld(0).spigotConfig; + }else{ + return true; + } + + return ((entity.activationType == 3 && config.miscActivationRange == 0) + || (entity.activationType == 2 && config.animalActivationRange == 0) + || (entity.activationType == 1 && config.monsterActivationRange == 0) + || entity instanceof EntityPlayer + || entity instanceof EntityThrowable + || entity instanceof MultiPartEntityPart + || entity instanceof EntityWither + || entity instanceof EntityFireball + || entity instanceof EntityFallingBlock + || entity instanceof EntityWeatherEffect + || entity instanceof EntityTNTPrimed + || entity instanceof EntityEnderCrystal + || entity instanceof EntityFireworkRocket + || (entity.getClass().getSuperclass() == Entity.class && !entity.isCreatureType(EnumCreatureType.CREATURE, false)) + && !entity.isCreatureType(EnumCreatureType.AMBIENT, false) && !entity.isCreatureType(EnumCreatureType.MONSTER, false) + && !entity.isCreatureType(EnumCreatureType.WATER_CREATURE, false) + ); + } + + /** + * Find what entities are in range of the players in the world and set + * active if in range. + * + * @param world + */ + public static void activateEntities(World world) + { + final int miscActivationRange = world.spigotConfig.miscActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); + maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); + + for ( EntityPlayer player : world.playerEntities ) + { + + player.activatedTick = MinecraftServer.currentTick; + maxBB = player.getEntityBoundingBox().grow( maxRange, 256, maxRange ); + miscBB = player.getEntityBoundingBox().grow( miscActivationRange, 256, miscActivationRange ); + animalBB = player.getEntityBoundingBox().grow( animalActivationRange, 256, animalActivationRange ); + monsterBB = player.getEntityBoundingBox().grow( monsterActivationRange, 256, monsterActivationRange ); + + int i = MathHelper.floor( maxBB.minX / 16.0D ); + int j = MathHelper.floor( maxBB.maxX / 16.0D ); + int k = MathHelper.floor( maxBB.minZ / 16.0D ); + int l = MathHelper.floor( maxBB.maxZ / 16.0D ); + + for ( int i1 = i; i1 <= j; ++i1 ) + { + for ( int j1 = k; j1 <= l; ++j1 ) + { + if ( world.getWorld().isChunkLoaded( i1, j1 ) ) + { + activateChunkEntities( world.getChunkFromChunkCoords( i1, j1 ) ); + } + } + } + } + } + + /** + * Checks for the activation state of all entities in this chunk. + * + * @param chunk + */ + private static void activateChunkEntities(Chunk chunk) + { + for ( ClassInheritanceMultiMap slice : chunk.entityLists ) + { + for ( Entity entity : slice ) + { + if(entity == null){ + continue; + } + if ( MinecraftServer.currentTick > entity.activatedTick ) + { + if ( entity.defaultActivationState ) + { + entity.activatedTick = MinecraftServer.currentTick; + continue; + } + switch ( entity.activationType ) + { + case 1: + if ( monsterBB.intersects( entity.getEntityBoundingBox() ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 2: + if ( animalBB.intersects( entity.getEntityBoundingBox() ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 3: + default: + if ( miscBB.intersects( entity.getEntityBoundingBox() ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + } + } + } + } + } + + /** + * If an entity is not in range, do some more checks to see if we should + * give it a shot. + * + * @param entity + * @return + */ + public static boolean checkEntityImmunities(Entity entity) + { + // quick checks. + if ( entity.inWater || entity.fire > 0 ) + { + return true; + } + if ( !( entity instanceof EntityArrow) ) + { + if ( !entity.onGround || !entity.riddenByEntities.isEmpty() || entity.isRiding() ) + { + return true; + } + } else if ( !( (EntityArrow) entity ).inGround ) + { + return true; + } + // special cases. + if ( entity instanceof EntityLiving) + { + EntityLiving living = (EntityLiving) entity; + if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime> 0 || living.activePotionsMap.size() > 0 ) + { + return true; + } + if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).getAttackTarget() != null ) + { + return true; + } + if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).isMating() ) + { + return true; + } + if ( entity instanceof EntityAnimal) + { + EntityAnimal animal = (EntityAnimal) entity; + if ( animal.isChild() || animal.isInLove() ) + { + return true; + } + if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).getSheared() ) + { + return true; + } + } + if (entity instanceof EntityCreeper && ((EntityCreeper) entity).hasIgnited()) { // isExplosive + return true; + } + } + return false; + } + + /** + * Checks if the entity is active for this tick. + * + * @param entity + * @return + */ + public static boolean checkIfActive(Entity entity) + { + // Never safe to skip fireworks or entities not yet added to chunk + // PAIL: inChunk - boolean under datawatchers + if ( !entity.addedToChunk || entity instanceof EntityFireworkRocket ) { + return true; + } + + boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; + + // Should this entity tick? + if ( !isActive ) + { + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. + if ( checkEntityImmunities( entity ) ) + { + // Triggered some sort of immunity, give 20 full ticks before we check again. + entity.activatedTick = MinecraftServer.currentTick + 20; + } + isActive = true; + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. + } else if ( !entity.defaultActivationState && entity.ticksExisted % 4 == 0 && !checkEntityImmunities( entity ) ) + { + isActive = false; + } + int x = MathHelper.floor( entity.posX ); + int z = MathHelper.floor( entity.posZ ); + // Make sure not on edge of unloaded chunk + Chunk chunk = entity.world.getChunkIfLoaded( x >> 4, z >> 4 ); + if ( isActive && !( chunk != null ) ) + { + isActive = false; + } + return isActive; + } +} diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java new file mode 100644 index 00000000..360fe372 --- /dev/null +++ b/src/main/java/org/spigotmc/AsyncCatcher.java @@ -0,0 +1,17 @@ +package org.spigotmc; + +import net.minecraft.server.MinecraftServer; + +public class AsyncCatcher +{ + + public static boolean enabled = true; + + public static void catchOp(String reason) + { + if ( enabled && Thread.currentThread() != MinecraftServer.getServerInstance().primaryThread ) + { + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } + } +} diff --git a/src/main/java/org/spigotmc/CaseInsensitiveHashingStrategy.java b/src/main/java/org/spigotmc/CaseInsensitiveHashingStrategy.java new file mode 100644 index 00000000..aafdd368 --- /dev/null +++ b/src/main/java/org/spigotmc/CaseInsensitiveHashingStrategy.java @@ -0,0 +1,18 @@ +package org.spigotmc; + +import gnu.trove.strategy.HashingStrategy; + +class CaseInsensitiveHashingStrategy implements HashingStrategy { + + static final CaseInsensitiveHashingStrategy INSTANCE = new CaseInsensitiveHashingStrategy(); + + @Override + public int computeHashCode(Object object) { + return ((String) object).toLowerCase().hashCode(); + } + + @Override + public boolean equals(Object o1, Object o2) { + return o1.equals(o2) || (o1 instanceof String && o2 instanceof String && ((String) o1).toLowerCase().equals(((String) o2).toLowerCase())); + } +} diff --git a/src/main/java/org/spigotmc/CaseInsensitiveMap.java b/src/main/java/org/spigotmc/CaseInsensitiveMap.java new file mode 100644 index 00000000..1934fd50 --- /dev/null +++ b/src/main/java/org/spigotmc/CaseInsensitiveMap.java @@ -0,0 +1,15 @@ +package org.spigotmc; + +import gnu.trove.map.hash.TCustomHashMap; +import java.util.Map; + +public class CaseInsensitiveMap extends TCustomHashMap { + + public CaseInsensitiveMap() { + super(CaseInsensitiveHashingStrategy.INSTANCE); + } + + public CaseInsensitiveMap(Map map) { + super(CaseInsensitiveHashingStrategy.INSTANCE, map); + } +} diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java new file mode 100644 index 00000000..dad44491 --- /dev/null +++ b/src/main/java/org/spigotmc/CustomTimingsHandler.java @@ -0,0 +1,158 @@ +package org.spigotmc; + +import org.bukkit.command.defaults.TimingsCommand; +import java.io.PrintStream; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.bukkit.Bukkit; +import org.bukkit.World; + +/** + * Provides custom timing sections for /timings merged. + */ +public class CustomTimingsHandler +{ + + private static Queue HANDLERS = new ConcurrentLinkedQueue(); + /*========================================================================*/ + private final String name; + private final CustomTimingsHandler parent; + private long count = 0; + private long start = 0; + private long timingDepth = 0; + private long totalTime = 0; + private long curTickTotal = 0; + private long violations = 0; + + public CustomTimingsHandler(String name) + { + this( name, null ); + } + + public CustomTimingsHandler(String name, CustomTimingsHandler parent) + { + this.name = name; + this.parent = parent; + HANDLERS.add( this ); + } + + /** + * Prints the timings and extra data to the given stream. + * + * @param printStream + */ + public static void printTimings(PrintStream printStream) + { + printStream.println( "Minecraft" ); + for ( CustomTimingsHandler timings : HANDLERS ) + { + long time = timings.totalTime; + long count = timings.count; + if ( count == 0 ) + { + continue; + } + long avg = time / count; + + printStream.println( " " + timings.name + " Time: " + time + " Count: " + count + " Avg: " + avg + " Violations: " + timings.violations ); + } + printStream.println( "# Version " + Bukkit.getVersion() ); + int entities = 0; + int livingEntities = 0; + for ( World world : Bukkit.getWorlds() ) + { + entities += world.getEntities().size(); + livingEntities += world.getLivingEntities().size(); + } + printStream.println( "# Entities " + entities ); + printStream.println( "# LivingEntities " + livingEntities ); + } + + /** + * Resets all timings. + */ + public static void reload() + { + if ( Bukkit.getPluginManager().useTimings() ) + { + for ( CustomTimingsHandler timings : HANDLERS ) + { + timings.reset(); + } + } + TimingsCommand.timingStart = System.nanoTime(); + } + + /** + * Ticked every tick by CraftBukkit to count the number of times a timer + * caused TPS loss. + */ + public static void tick() + { + if ( Bukkit.getPluginManager().useTimings() ) + { + for ( CustomTimingsHandler timings : HANDLERS ) + { + if ( timings.curTickTotal > 50000000 ) + { + timings.violations += Math.ceil( timings.curTickTotal / 50000000 ); + } + timings.curTickTotal = 0; + timings.timingDepth = 0; // incase reset messes this up + } + } + } + + /** + * Starts timing to track a section of code. + */ + public void startTiming() + { + // If second condtion fails we are already timing + if ( Bukkit.getPluginManager().useTimings() && ++timingDepth == 1 ) + { + start = System.nanoTime(); + if ( parent != null && ++parent.timingDepth == 1 ) + { + parent.start = start; + } + } + } + + /** + * Stops timing a section of code. + */ + public void stopTiming() + { + if ( Bukkit.getPluginManager().useTimings() ) + { + if ( --timingDepth != 0 || start == 0 ) + { + return; + } + long diff = System.nanoTime() - start; + totalTime += diff; + curTickTotal += diff; + count++; + start = 0; + if ( parent != null ) + { + parent.stopTiming(); + } + } + } + + /** + * Reset this timer, setting all values to zero. + */ + public void reset() + { + count = 0; + violations = 0; + curTickTotal = 0; + totalTime = 0; + start = 0; + timingDepth = 0; + } +} diff --git a/src/main/java/org/spigotmc/LimitStream.java b/src/main/java/org/spigotmc/LimitStream.java new file mode 100644 index 00000000..e9e90950 --- /dev/null +++ b/src/main/java/org/spigotmc/LimitStream.java @@ -0,0 +1,39 @@ +package org.spigotmc; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import net.minecraft.nbt.NBTSizeTracker; + +public class LimitStream extends FilterInputStream +{ + + private final NBTSizeTracker limit; + + public LimitStream(InputStream is, NBTSizeTracker limit) + { + super( is ); + this.limit = limit; + } + + @Override + public int read() throws IOException + { + limit.read( 8 ); + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException + { + limit.read( b.length * 8 ); + return super.read( b ); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + limit.read( len * 8 ); + return super.read( b, off, len ); + } +} diff --git a/src/main/java/org/spigotmc/Metrics.java b/src/main/java/org/spigotmc/Metrics.java new file mode 100644 index 00000000..eb70cf95 --- /dev/null +++ b/src/main/java/org/spigotmc/Metrics.java @@ -0,0 +1,645 @@ +/* + * Copyright 2011-2013 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ +package org.spigotmc; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.scheduler.BukkitTask; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import net.minecraft.server.MinecraftServer; + +/** + *

    The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.

    + * Public methods provided by this class:

    + * + * Graph createGraph(String name);
    + * void addCustomData(BukkitMetrics.Plotter plotter);
    + * void start();
    + *
    + */ +public class Metrics { + + /** + * The current revision number + */ + private final static int REVISION = 6; + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + /** + * The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and + * want to change it. + */ + private static final String CUSTOM_DATA_SEPARATOR = "~~"; + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 10; + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + /** + * The default graph, used for addCustomData when you don't want a specific graph + */ + private final Graph defaultGraph = new Graph("Default"); + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + /** + * The plugin configuration file + */ + private final File configurationFile; + /** + * Unique server id + */ + private final String guid; + /** + * Debug mode + */ + private final boolean debug; + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + /** + * The scheduled task + */ + private volatile Timer task = null; + + public Metrics() throws IOException { + // load the config + configurationFile = getConfigFile(); + configuration = YamlConfiguration.loadConfiguration(configurationFile); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + configuration.addDefault("debug", false); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://mcstats.org").copyDefaults(true); + configuration.save(configurationFile); + } + + // Load the guid then + guid = configuration.getString("guid"); + debug = configuration.getBoolean("debug", false); + } + + /** + * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics + * website. Plotters can be added to the graph object returned. + * + * @param name The name of the graph + * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given + */ + public Graph createGraph(final String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + + // Construct the graph object + final Graph graph = new Graph(name); + + // Now we can add our graph + graphs.add(graph); + + // and return back + return graph; + } + + /** + * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend + * + * @param graph The name of the graph + */ + public void addGraph(final Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + + graphs.add(graph); + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter The plotter to use to plot custom data + */ + public void addCustomData(final Plotter plotter) { + if (plotter == null) { + throw new IllegalArgumentException("Plotter cannot be null"); + } + + // Add the plotter to the graph o/ + defaultGraph.addPlotter(plotter); + + // Ensure the default graph is included in the submitted graphs + graphs.add(defaultGraph); + } + + /** + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the + * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200 + * ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (task != null) { + return true; + } + + // Begin hitting the server with glorious data + task = new Timer("Spigot Metrics Thread", true); + + task.scheduleAtFixedRate(new TimerTask() { + private boolean firstPost = true; + + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && task != null) { + task.cancel(); + task = null; + // Tell all plotters to stop gathering information. + for (Graph graph : graphs) { + graph.onOptOut(); + } + } + } + + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + } + }, 0, TimeUnit.MINUTES.toMillis(PING_INTERVAL)); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized (optOutLock) { + try { + // Reload the metrics file + configuration.load(getConfigFile()); + } catch (IOException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } catch (InvalidConfigurationException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } + return configuration.getBoolean("opt-out", false); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } + + // Enable Task, if it is not running + if (task == null) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (task != null) { + task.cancel(); + task = null; + } + } + } + + /** + * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use + // is to abuse the plugin object we already have + // plugin.getDataFolder() => base/plugins/PluginA/ + // pluginsFolder => base/plugins/ + // The base is not necessarily relative to the startup directory. + // File pluginsFolder = plugin.getDataFolder().getParentFile(); + + // return => base/plugins/PluginMetrics/config.yml + return new File(new File((File) MinecraftServer.getServerInstance().options.valueOf("plugins"), "PluginMetrics"), "config.yml"); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(final boolean isPing) throws IOException { + // Server software specific section + String pluginName = "Spigot"; + boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled + String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"; + String serverVersion = Bukkit.getVersion(); + int playersOnline = Bukkit.getServer().getOnlinePlayers().size(); + + // END server software specific section -- all code below does not use any code outside of this class / Java + + // Construct the post data + final StringBuilder data = new StringBuilder(); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc + data.append(encode("guid")).append('=').append(encode(guid)); + encodeDataPair(data, "version", pluginVersion); + encodeDataPair(data, "server", serverVersion); + encodeDataPair(data, "players", Integer.toString(playersOnline)); + encodeDataPair(data, "revision", String.valueOf(REVISION)); + + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + encodeDataPair(data, "osname", osname); + encodeDataPair(data, "osarch", osarch); + encodeDataPair(data, "osversion", osversion); + encodeDataPair(data, "cores", Integer.toString(coreCount)); + encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); + encodeDataPair(data, "java_version", java_version); + + // If we're pinging, append it + if (isPing) { + encodeDataPair(data, "ping", "true"); + } + + // Acquire a lock on the graphs, which lets us make the assumption we also lock everything + // inside of the graph (e.g plotters) + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + // The key name to send to the metrics server + // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top + // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME + final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); + + // The value to send, which for the foreseeable future is just the string + // value of plotter.getValue() + final String value = Integer.toString(plotter.getValue()); + + // Add it to the http post data :) + encodeDataPair(data, key, value); + } + } + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data.toString()); + writer.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + final String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response == null || response.startsWith("ERR")) { + throw new IOException(response); //Throw the exception + } else { + // Is this the first update this hour? + if (response.contains("OK This is your first update this hour")) { + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + *

    Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair + * MUST be included manually, e.g:

    + * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer the stringbuilder to append the data pair onto + * @param key the key value + * @param value the value + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is + * rejected + */ + private final String name; + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet(); + + private Graph(final String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return the Graph's name + */ + public String getName() { + return name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter the plotter to add to the graph + */ + public void addPlotter(final Plotter plotter) { + plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter the plotter to remove from the graph + */ + public void removePlotter(final Plotter plotter) { + plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return an unmodifiable {@link Set} of the plotter objects + */ + public Set getPlotters() { + return Collections.unmodifiableSet(plotters); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Graph)) { + return false; + } + + final Graph graph = (Graph) object; + return graph.name.equals(name); + } + + /** + * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. + */ + protected void onOptOut() { + } + } + + /** + * Interface used to collect custom data for a plugin + */ + public static abstract class Plotter { + + /** + * The plot's name + */ + private final String name; + + /** + * Construct a plotter with the default plot name + */ + public Plotter() { + this("Default"); + } + + /** + * Construct a plotter with a specific plot name + * + * @param name the name of the plotter to use, which will show up on the website + */ + public Plotter(final String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point. Since this function defers to an external function it may or may + * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called + * from any thread so care should be taken when accessing resources that need to be synchronized. + * + * @return the current value for the point to be plotted. + */ + public abstract int getValue(); + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return name; + } + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + final Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } + } +} diff --git a/src/main/java/org/spigotmc/SlackActivityAccountant.java b/src/main/java/org/spigotmc/SlackActivityAccountant.java new file mode 100644 index 00000000..48affcdd --- /dev/null +++ b/src/main/java/org/spigotmc/SlackActivityAccountant.java @@ -0,0 +1,80 @@ +package org.spigotmc; + +import net.minecraft.server.MinecraftServer; + +/** + * Keeps track of the time spent doing main thread activities that can be spread across ticks, + * so that such work doesn't exceed the current tick's estimated available slack time. Each + * activity is allotted a proportion of the expected slack time according to its weight, versus the + * estimated total weight of all activities. + */ +public class SlackActivityAccountant { + private double prevTickSlackWeightReciprocal = 1 / MIN_SLACK_WEIGHT; + private static final double MIN_SLACK_WEIGHT = 1 / 65536.0; + private double averageTickNonSlackNanos = 0; + private static final double AVERAGING_FACTOR = 0.375; + + private long currentActivityStartNanos; + private static final long OFF = -1; + private long currentActivityEndNanos; + private double tickSlackWeight; + private long tickSlackNanos; + + private double getSlackFraction(double slackWeight) { + return Math.min(slackWeight * this.prevTickSlackWeightReciprocal, 1); + } + + private int getEstimatedSlackNanos() { + return (int) Math.max(MinecraftServer.currentTick - (long) this.averageTickNonSlackNanos, 0); + } + + public void tickStarted() { + this.currentActivityStartNanos = OFF; + this.tickSlackWeight = 0; + this.tickSlackNanos = 0; + } + + public void startActivity(double slackWeight) { + double slackFraction0 = getSlackFraction(this.tickSlackWeight); + this.tickSlackWeight += slackWeight; + double slackFraction1 = getSlackFraction(this.tickSlackWeight); + + long t = System.nanoTime(); + this.currentActivityStartNanos = t; + this.currentActivityEndNanos = t + ((int) ((slackFraction1 - slackFraction0) * this.getEstimatedSlackNanos())); + } + + private void endActivity(long endNanos) { + this.tickSlackNanos += endNanos - this.currentActivityStartNanos; + this.currentActivityStartNanos = OFF; + } + + public boolean activityTimeIsExhausted() { + if (this.currentActivityStartNanos == OFF) { + return true; + } + + long t = System.nanoTime(); + if (t <= this.currentActivityEndNanos) { + return false; + } else { + this.endActivity(this.currentActivityEndNanos); + return true; + } + } + + public void endActivity() { + if (this.currentActivityStartNanos == OFF) { + return; + } + + this.endActivity(Math.min(System.nanoTime(), this.currentActivityEndNanos)); + } + + public void tickEnded(long tickNanos) { + this.prevTickSlackWeightReciprocal = 1 / Math.max(this.tickSlackWeight, MIN_SLACK_WEIGHT); + + long tickNonSlackNanos = tickNanos - this.tickSlackNanos; + this.averageTickNonSlackNanos = this.averageTickNonSlackNanos * (1 - AVERAGING_FACTOR) + tickNonSlackNanos * AVERAGING_FACTOR; + } +} diff --git a/src/main/java/org/spigotmc/SneakyThrow.java b/src/main/java/org/spigotmc/SneakyThrow.java new file mode 100644 index 00000000..31fc0a98 --- /dev/null +++ b/src/main/java/org/spigotmc/SneakyThrow.java @@ -0,0 +1,15 @@ +package org.spigotmc; + +public class SneakyThrow +{ + + public static void sneaky(Throwable t) + { + throw SneakyThrow.superSneaky( t ); + } + + private static T superSneaky(Throwable t) throws T + { + throw (T) t; + } +} diff --git a/src/main/java/org/spigotmc/SpigotCommand.java b/src/main/java/org/spigotmc/SpigotCommand.java new file mode 100644 index 00000000..28ef5e02 --- /dev/null +++ b/src/main/java/org/spigotmc/SpigotCommand.java @@ -0,0 +1,44 @@ +package org.spigotmc; + +import java.io.File; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.WorldServer; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +public class SpigotCommand extends Command { + + public SpigotCommand(String name) { + super(name); + this.description = "Spigot related commands"; + this.usageMessage = "/spigot reload"; + this.setPermission("bukkit.command.spigot"); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + + if (args.length != 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + + if (args[0].equals("reload")) { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + + MinecraftServer console = MinecraftServer.getServerCB(); + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); + for (WorldServer world : console.worlds) { + world.spigotConfig.init(); + } + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete."); + } + + return true; + } +} diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java new file mode 100644 index 00000000..312ca701 --- /dev/null +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -0,0 +1,351 @@ +package org.spigotmc; + +import com.google.common.base.Throwables; +import gnu.trove.map.hash.TObjectIntHashMap; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.logging.Level; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.RangedAttribute; +import net.minecraft.server.MinecraftServer; +import net.minecraft.stats.StatList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +public class SpigotConfig +{ + + private static File CONFIG_FILE; + private static final String HEADER = "This is the main configuration file for Spigot.\n" + + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" + + "with caution, and make sure you know what each option does before configuring.\n" + + "For a reference for any variable inside this file, check out the Spigot wiki at\n" + + "http://www.spigotmc.org/wiki/spigot-configuration/\n" + + "\n" + + "If you need help with the configuration or have any questions related to Spigot,\n" + + "join us at the IRC or drop by our forums and leave a post.\n" + + "\n" + + "IRC: #spigot @ irc.spi.gt ( http://www.spigotmc.org/pages/irc/ )\n" + + "Forums: http://www.spigotmc.org/\n"; + /*========================================================================*/ + public static YamlConfiguration config; + static int version; + static Map commands; + /*========================================================================*/ + + public static void init(File configFile) + { + CONFIG_FILE = configFile; + config = new YamlConfiguration(); + try + { + config.load( CONFIG_FILE ); + } catch ( IOException ex ) + { + } catch ( InvalidConfigurationException ex ) + { + Bukkit.getLogger().log( Level.SEVERE, "Could not load spigot.yml, please correct your syntax errors", ex ); + throw Throwables.propagate( ex ); + } + + config.options().header( HEADER ); + config.options().copyDefaults( true ); + + commands = new HashMap(); + + version = getInt( "config-version", 11 ); + set( "config-version", 11 ); + readConfig( SpigotConfig.class, null ); + } + + public static void registerCommands() + { + for ( Map.Entry entry : commands.entrySet() ) + { + MinecraftServer.getServerCB().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() ); + } + } + + static void readConfig(Class clazz, Object instance) + { + for ( Method method : clazz.getDeclaredMethods() ) + { + if ( Modifier.isPrivate( method.getModifiers() ) ) + { + if ( method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE ) + { + try + { + method.setAccessible( true ); + method.invoke( instance ); + } catch ( InvocationTargetException ex ) + { + throw Throwables.propagate( ex.getCause() ); + } catch ( Exception ex ) + { + Bukkit.getLogger().log( Level.SEVERE, "Error invoking " + method, ex ); + } + } + } + } + + try + { + config.save( CONFIG_FILE ); + } catch ( IOException ex ) + { + Bukkit.getLogger().log( Level.SEVERE, "Could not save " + CONFIG_FILE, ex ); + } + } + + private static void set(String path, Object val) + { + config.set( path, val ); + } + + private static boolean getBoolean(String path, boolean def) + { + config.addDefault( path, def ); + return config.getBoolean( path, config.getBoolean( path ) ); + } + + private static int getInt(String path, int def) + { + config.addDefault( path, def ); + return config.getInt( path, config.getInt( path ) ); + } + + private static List getList(String path, T def) + { + config.addDefault( path, def ); + return (List) config.getList( path, config.getList( path ) ); + } + + private static String getString(String path, String def) + { + config.addDefault( path, def ); + return config.getString( path, config.getString( path ) ); + } + + private static double getDouble(String path, double def) + { + config.addDefault( path, def ); + return config.getDouble( path, config.getDouble( path ) ); + } + + public static boolean logCommands; + private static void logCommands() + { + logCommands = getBoolean( "commands.log", true ); + } + + public static int tabComplete; + private static void tabComplete() + { + if ( version < 6 ) + { + boolean oldValue = getBoolean( "commands.tab-complete", true ); + if ( oldValue ) + { + set( "commands.tab-complete", 0 ); + } else + { + set( "commands.tab-complete", -1 ); + } + } + tabComplete = getInt( "commands.tab-complete", 0 ); + } + + public static String whitelistMessage; + public static String unknownCommandMessage; + public static String serverFullMessage; + public static String outdatedClientMessage = "Outdated client! Please use {0}"; + public static String outdatedServerMessage = "Outdated server! I\'m still on {0}"; + private static String transform(String s) + { + return ChatColor.translateAlternateColorCodes( '&', s ).replaceAll( "\\\\n", "\n" ); + } + private static void messages() + { + if (version < 8) + { + set( "messages.outdated-client", outdatedClientMessage ); + set( "messages.outdated-server", outdatedServerMessage ); + } + + whitelistMessage = transform( getString( "messages.whitelist", "You are not whitelisted on this server!" ) ); + unknownCommandMessage = transform( getString( "messages.unknown-command", "Unknown command. Type \"/help\" for help." ) ); + serverFullMessage = transform( getString( "messages.server-full", "The server is full!" ) ); + outdatedClientMessage = transform( getString( "messages.outdated-client", outdatedClientMessage ) ); + outdatedServerMessage = transform( getString( "messages.outdated-server", outdatedServerMessage ) ); + } + + public static int timeoutTime = 60; + public static boolean restartOnCrash = true; + public static String restartScript = "./start.sh"; + public static String restartMessage; + private static void watchdog() + { + timeoutTime = getInt( "settings.timeout-time", timeoutTime ); + restartOnCrash = getBoolean( "settings.restart-on-crash", restartOnCrash ); + restartScript = getString( "settings.restart-script", restartScript ); + restartMessage = transform( getString( "messages.restart", "Server is restarting" ) ); + WatchdogThread.doStart( timeoutTime, restartOnCrash ); + } + + public static boolean bungee; + private static void bungee() { + if ( version < 4 ) + { + set( "settings.bungeecord", false ); + System.out.println( "Oudated config, disabling BungeeCord support!" ); + } + bungee = getBoolean( "settings.bungeecord", false ); + } + + private static void nettyThreads() + { + int count = getInt( "settings.netty-threads", 4 ); + System.setProperty( "io.netty.eventLoopThreads", Integer.toString( count ) ); + Bukkit.getLogger().log( Level.INFO, "Using {0} threads for Netty based IO", count ); + } + + public static boolean lateBind; + private static void lateBind() { + lateBind = getBoolean( "settings.late-bind", false ); + } + + public static boolean disableStatSaving; + public static TObjectIntHashMap forcedStats = new TObjectIntHashMap(); + private static void stats() + { + disableStatSaving = getBoolean( "stats.disable-saving", false ); + + if ( !config.contains( "stats.forced-stats" ) ) { + config.createSection( "stats.forced-stats" ); + } + + ConfigurationSection section = config.getConfigurationSection( "stats.forced-stats" ); + for ( String name : section.getKeys( true ) ) + { + if ( section.isInt( name ) ) + { + if ( StatList.getOneShotStat(name) == null ) + { + Bukkit.getLogger().log(Level.WARNING, "Ignoring non existent stats.forced-stats " + name); + continue; + } + forcedStats.put( name, section.getInt( name ) ); + } + } + } + + public static int playerSample; + private static void playerSample() + { + playerSample = Math.max(getInt( "settings.sample-count", 12 ), 0); // Paper - Avoid negative counts + LogManager.getLogger("Spigot").info( "Server Ping Player Sample Count: " + playerSample ); + } + + public static int playerShuffle; + private static void playerShuffle() + { + playerShuffle = getInt( "settings.player-shuffle", 0 ); + } + + public static List spamExclusions; + private static void spamExclusions() + { + spamExclusions = getList( "commands.spam-exclusions", Arrays.asList("/skill") ); + } + + public static boolean silentCommandBlocks; + private static void silentCommandBlocks() + { + silentCommandBlocks = getBoolean( "commands.silent-commandblock-console", false ); + } + + public static boolean filterCreativeItems; + private static void filterCreativeItems() + { + filterCreativeItems = getBoolean( "settings.filter-creative-items", true ); + } + + public static Set replaceCommands; + private static void replaceCommands() + { + if ( config.contains( "replace-commands" ) ) + { + set( "commands.replace-commands", config.getStringList( "replace-commands" ) ); + config.set( "replace-commands", null ); + } + replaceCommands = new HashSet( (List) getList( "commands.replace-commands", + Arrays.asList( "setblock", "summon", "testforblock", "tellraw" ) ) ); + } + + public static int userCacheCap; + private static void userCacheCap() + { + userCacheCap = getInt( "settings.user-cache-size", 1000 ); + } + + public static boolean saveUserCacheOnStopOnly; + private static void saveUserCacheOnStopOnly() + { + saveUserCacheOnStopOnly = getBoolean( "settings.save-user-cache-on-stop-only", false ); + } + + public static int intCacheLimit; + private static void intCacheLimit() + { + intCacheLimit = getInt( "settings.int-cache-limit", 1024 ); + } + + public static double movedWronglyThreshold; + private static void movedWronglyThreshold() + { + movedWronglyThreshold = getDouble( "settings.moved-wrongly-threshold", 0.0625D ); + } + + public static double movedTooQuicklyMultiplier; + private static void movedTooQuicklyMultiplier() + { + movedTooQuicklyMultiplier = getDouble( "settings.moved-too-quickly-multiplier", 10.0D ); + } + + public static double maxHealth = 2048; + public static double movementSpeed = 2048; + public static double attackDamage = 2048; + private static void attributeMaxes() + { + maxHealth = getDouble( "settings.attribute.maxHealth.max", maxHealth ); + ( (RangedAttribute) SharedMonsterAttributes.MAX_HEALTH ).maximumValue = maxHealth; + movementSpeed = getDouble( "settings.attribute.movementSpeed.max", movementSpeed ); + ( (RangedAttribute) SharedMonsterAttributes.MOVEMENT_SPEED ).maximumValue = movementSpeed; + attackDamage = getDouble( "settings.attribute.attackDamage.max", attackDamage ); + ( (RangedAttribute) SharedMonsterAttributes.ATTACK_DAMAGE ).maximumValue = attackDamage; + } + + public static int itemDirtyTicks; + private static void itemDirtyTicks() { + itemDirtyTicks = getInt("settings.item-dirty-ticks", 20); + } + + public static boolean disableAdvancementSaving; + public static List disabledAdvancements; + private static void disabledAdvancements() { + disableAdvancementSaving = getBoolean("advancements.disable-saving", false); + disabledAdvancements = getList("advancements.disabled", Arrays.asList("minecraft:story/disabled")); + } +} diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java new file mode 100644 index 00000000..7d0fcfb9 --- /dev/null +++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java @@ -0,0 +1,319 @@ +package org.spigotmc; + +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; + +public class SpigotWorldConfig +{ + + private final String worldName; + private final YamlConfiguration config; + private boolean verbose; + + public SpigotWorldConfig(String worldName) + { + this.worldName = worldName; + this.config = SpigotConfig.config; + init(); + } + + public void init() + { + this.verbose = getBoolean( "verbose", true ); + + log( "-------- World Settings For [" + worldName + "] --------" ); + SpigotConfig.readConfig( SpigotWorldConfig.class, this ); + } + + private void log(String s) + { + if ( verbose ) + { + Bukkit.getLogger().info( s ); + } + } + + private void set(String path, Object val) + { + config.set( "world-settings.default." + path, val ); + } + + private boolean getBoolean(String path, boolean def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getBoolean( "world-settings." + worldName + "." + path, config.getBoolean( "world-settings.default." + path ) ); + } + + private double getDouble(String path, double def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getDouble( "world-settings." + worldName + "." + path, config.getDouble( "world-settings.default." + path ) ); + } + + private int getInt(String path, int def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getInt( "world-settings." + worldName + "." + path, config.getInt( "world-settings.default." + path ) ); + } + + private List getList(String path, T def) + { + config.addDefault( "world-settings.default." + path, def ); + return (List) config.getList( "world-settings." + worldName + "." + path, config.getList( "world-settings.default." + path ) ); + } + + private String getString(String path, String def) + { + config.addDefault( "world-settings.default." + path, def ); + return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) ); + } + + // Crop growth rates + public int cactusModifier; + public int caneModifier; + public int melonModifier; + public int mushroomModifier; + public int pumpkinModifier; + public int saplingModifier; + public int wheatModifier; + public int wartModifier; + public int vineModifier; + public int cocoaModifier; + private int getAndValidateGrowth(String crop) + { + int modifier = getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 ); + if ( modifier == 0 ) + { + log( "Cannot set " + crop + " growth to zero, defaulting to 100" ); + modifier = 100; + } + log( crop + " Growth Modifier: " + modifier + "%" ); + + return modifier; + } + private void growthModifiers() + { + cactusModifier = getAndValidateGrowth( "Cactus" ); + caneModifier = getAndValidateGrowth( "Cane" ); + melonModifier = getAndValidateGrowth( "Melon" ); + mushroomModifier = getAndValidateGrowth( "Mushroom" ); + pumpkinModifier = getAndValidateGrowth( "Pumpkin" ); + saplingModifier = getAndValidateGrowth( "Sapling" ); + wheatModifier = getAndValidateGrowth( "Wheat" ); + wartModifier = getAndValidateGrowth( "NetherWart" ); + vineModifier = getAndValidateGrowth( "Vine" ); + cocoaModifier = getAndValidateGrowth( "Cocoa" ); + } + + public double itemMerge; + private void itemMerge() + { + itemMerge = getDouble("merge-radius.item", 2.5 ); + log( "Item Merge Radius: " + itemMerge ); + } + + public double expMerge; + private void expMerge() + { + expMerge = getDouble("merge-radius.exp", 3.0 ); + log( "Experience Merge Radius: " + expMerge ); + } + + public int viewDistance; + private void viewDistance() + { + viewDistance = getInt( "view-distance", Bukkit.getViewDistance() ); + log( "View Distance: " + viewDistance ); + } + + public byte mobSpawnRange; + private void mobSpawnRange() + { + mobSpawnRange = (byte) getInt( "mob-spawn-range", 4 ); + log( "Mob Spawn Range: " + mobSpawnRange ); + } + + public int itemDespawnRate; + private void itemDespawnRate() + { + itemDespawnRate = getInt( "item-despawn-rate", 6000 ); + log( "Item Despawn Rate: " + itemDespawnRate ); + } + + public int animalActivationRange = 32; + public int monsterActivationRange = 32; + public int miscActivationRange = 16; + public int waterActivationRange = 16; // Paper + public boolean tickInactiveVillagers = true; + private void activationRange() + { + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); + waterActivationRange = getInt( "entity-activation-range.water", waterActivationRange ); // Paper + tickInactiveVillagers = getBoolean( "entity-activation-range.tick-inactive-villagers", tickInactiveVillagers ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Mi " + miscActivationRange + " / Tiv " + tickInactiveVillagers ); + } + + public int playerTrackingRange = 48; + public int animalTrackingRange = 48; + public int monsterTrackingRange = 48; + public int miscTrackingRange = 32; + public int otherTrackingRange = 64; + private void trackingRange() + { + playerTrackingRange = getInt( "entity-tracking-range.players", playerTrackingRange ); + animalTrackingRange = getInt( "entity-tracking-range.animals", animalTrackingRange ); + monsterTrackingRange = getInt( "entity-tracking-range.monsters", monsterTrackingRange ); + miscTrackingRange = getInt( "entity-tracking-range.misc", miscTrackingRange ); + otherTrackingRange = getInt( "entity-tracking-range.other", otherTrackingRange ); + log( "Entity Tracking Range: Pl " + playerTrackingRange + " / An " + animalTrackingRange + " / Mo " + monsterTrackingRange + " / Mi " + miscTrackingRange + " / Other " + otherTrackingRange ); + } + + public int hopperTransfer; + public int hopperCheck; + public int hopperAmount; + private void hoppers() + { + // Set the tick delay between hopper item movements + hopperTransfer = getInt( "ticks-per.hopper-transfer", 8 ); + if ( SpigotConfig.version < 11 ) + { + set( "ticks-per.hopper-check", 1 ); + } + hopperCheck = getInt( "ticks-per.hopper-check", 1 ); + hopperAmount = getInt( "hopper-amount", 1 ); + log( "Hopper Transfer: " + hopperTransfer + " Hopper Check: " + hopperCheck + " Hopper Amount: " + hopperAmount ); + } + + public boolean randomLightUpdates; + private void lightUpdates() + { + randomLightUpdates = getBoolean( "random-light-updates", false ); + log( "Random Lighting Updates: " + randomLightUpdates ); + } + + public boolean saveStructureInfo; + private void structureInfo() + { + saveStructureInfo = getBoolean( "save-structure-info", true ); + log( "Structure Info Saving: " + saveStructureInfo ); + if ( !saveStructureInfo ) + { + log( "*** WARNING *** You have selected to NOT save structure info. This may cause structures such as fortresses to not spawn mobs!" ); + log( "*** WARNING *** Please use this option with caution, SpigotMC is not responsible for any issues this option may cause in the future!" ); + } + } + + public int arrowDespawnRate; + private void arrowDespawnRate() + { + arrowDespawnRate = getInt( "arrow-despawn-rate", 1200 ); + log( "Arrow Despawn Rate: " + arrowDespawnRate ); + } + + public boolean zombieAggressiveTowardsVillager; + private void zombieAggressiveTowardsVillager() + { + zombieAggressiveTowardsVillager = getBoolean( "zombie-aggressive-towards-villager", true ); + log( "Zombie Aggressive Towards Villager: " + zombieAggressiveTowardsVillager ); + } + + public boolean nerfSpawnerMobs; + private void nerfSpawnerMobs() + { + nerfSpawnerMobs = getBoolean( "nerf-spawner-mobs", false ); + log( "Nerfing mobs spawned from spawners: " + nerfSpawnerMobs ); + } + + public boolean enableZombiePigmenPortalSpawns; + private void enableZombiePigmenPortalSpawns() + { + enableZombiePigmenPortalSpawns = getBoolean( "enable-zombie-pigmen-portal-spawns", true ); + log( "Allow Zombie Pigmen to spawn from portal blocks: " + enableZombiePigmenPortalSpawns ); + } + + public int dragonDeathSoundRadius; + private void keepDragonDeathPerWorld() + { + dragonDeathSoundRadius = getInt( "dragon-death-sound-radius", 0 ); + } + + public int witherSpawnSoundRadius; + private void witherSpawnSoundRadius() + { + witherSpawnSoundRadius = getInt( "wither-spawn-sound-radius", 0 ); + } + + public int villageSeed; + public int largeFeatureSeed; + public int monumentSeed; + public int slimeSeed; + private void initWorldGenSeeds() + { + villageSeed = getInt( "seed-village", 10387312 ); + largeFeatureSeed = getInt( "seed-feature", 14357617 ); + monumentSeed = getInt( "seed-monument", 10387313 ); + slimeSeed = getInt( "seed-slime", 987234911 ); + log( "Custom Map Seeds: Village: " + villageSeed + " Feature: " + largeFeatureSeed + " Monument: " + monumentSeed + " Slime: " + slimeSeed ); + } + + public float jumpWalkExhaustion; + public float jumpSprintExhaustion; + public float combatExhaustion; + public float regenExhaustion; + public float swimMultiplier; + public float sprintMultiplier; + public float otherMultiplier; + private void initHunger() + { + if ( SpigotConfig.version < 10 ) + { + set( "hunger.walk-exhaustion", null ); + set( "hunger.sprint-exhaustion", null ); + set( "hunger.combat-exhaustion", 0.1 ); + set( "hunger.regen-exhaustion", 6.0 ); + } + + jumpWalkExhaustion = (float) getDouble( "hunger.jump-walk-exhaustion", 0.05 ); + jumpSprintExhaustion = (float) getDouble( "hunger.jump-sprint-exhaustion", 0.2 ); + combatExhaustion = (float) getDouble( "hunger.combat-exhaustion", 0.1 ); + regenExhaustion = (float) getDouble( "hunger.regen-exhaustion", 6.0 ); + swimMultiplier = (float) getDouble( "hunger.swim-multiplier", 0.01 ); + sprintMultiplier = (float) getDouble( "hunger.sprint-multiplier", 0.1 ); + otherMultiplier = (float) getDouble( "hunger.other-multiplier", 0.0 ); + } + + public int currentPrimedTnt = 0; + public int maxTntTicksPerTick; + private void maxTntPerTick() { + if ( SpigotConfig.version < 7 ) + { + set( "max-tnt-per-tick", 100 ); + } + maxTntTicksPerTick = getInt( "max-tnt-per-tick", 100 ); + log( "Max TNT Explosions: " + maxTntTicksPerTick ); + } + + public int hangingTickFrequency; + private void hangingTickFrequency() + { + hangingTickFrequency = getInt( "hanging-tick-frequency", 100 ); + } + + public int tileMaxTickTime; + public int entityMaxTickTime; + private void maxTickTimes() + { + tileMaxTickTime = getInt("max-tick-time.tile", 50); + entityMaxTickTime = getInt("max-tick-time.entity", 50); + log("Tile Max Tick Time: " + tileMaxTickTime + "ms Entity max Tick Time: " + entityMaxTickTime + "ms"); + } + + public double squidSpawnRangeMin; + private void squidSpawnRange() + { + squidSpawnRangeMin = getDouble("squid-spawn-range.min", 45.0D); + } +} diff --git a/src/main/java/org/spigotmc/SupplierUtils.java b/src/main/java/org/spigotmc/SupplierUtils.java new file mode 100644 index 00000000..f63ce98b --- /dev/null +++ b/src/main/java/org/spigotmc/SupplierUtils.java @@ -0,0 +1,69 @@ +package org.spigotmc; + +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** + * Utilities for creating and working with {@code Supplier} instances. + */ +public class SupplierUtils { + + /** + * Repeatedly supplies the first value from a given sequence; the value is + * obtained only when required. + */ + public static class LazyHeadSupplier implements Supplier { + + private @Nullable Supplier completion; + private @Nullable V value; + + public LazyHeadSupplier(Supplier completion) { + this.completion = completion; + } + + public synchronized @Nullable V get() { + if (this.completion != null) { + this.value = this.completion.get(); + this.completion = null; + } + + return this.value; + } + } + + /** + * Repeatedly supplies the given value. + */ + public static class ValueSupplier implements Supplier { + + private final @Nullable V value; + + public ValueSupplier(@Nullable V value) { + this.value = value; + } + + public @Nullable V get() { + return this.value; + } + } + + /** + * Creates a new {@code Supplier} that supplies the given {@code Supplier}'s + * first value. + * + * @param doLazily {@code false}, if {@code completion.get()} should be + * called immediately, or {@code true}, if {@code completion.get()} should + * be called only when the value is first needed. + */ + public static Supplier createUnivaluedSupplier(Supplier completion, boolean doLazily) { + return doLazily ? new LazyHeadSupplier(completion) : new ValueSupplier(completion.get()); + } + + /** + * Returns {@code supplier.get()}, if {@code supplier} is non-{@code null} + * (or {@code null}, otherwise). + */ + public static @Nullable V getIfExists(@Nullable Supplier supplier) { + return supplier != null ? supplier.get() : null; + } +} diff --git a/src/main/java/org/spigotmc/TickLimiter.java b/src/main/java/org/spigotmc/TickLimiter.java new file mode 100644 index 00000000..23a39382 --- /dev/null +++ b/src/main/java/org/spigotmc/TickLimiter.java @@ -0,0 +1,20 @@ +package org.spigotmc; + +public class TickLimiter { + + private final int maxTime; + private long startTime; + + public TickLimiter(int maxtime) { + this.maxTime = maxtime; + } + + public void initTick() { + startTime = System.currentTimeMillis(); + } + + public boolean shouldContinue() { + long remaining = System.currentTimeMillis() - startTime; + return remaining < maxTime; + } +} diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java new file mode 100644 index 00000000..fc280ca7 --- /dev/null +++ b/src/main/java/org/spigotmc/TrackingRange.java @@ -0,0 +1,51 @@ +package org.spigotmc; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.entity.item.EntityPainting; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.monster.EntityGhast; +import net.minecraft.entity.player.EntityPlayer; + +public class TrackingRange +{ + + /** + * Gets the range an entity should be 'tracked' by players and visible in + * the client. + * + * @param entity + * @param defaultRange Default range defined by Mojang + * @return + */ + public static int getEntityTrackingRange(Entity entity, int defaultRange) + { + SpigotWorldConfig config = entity.world.spigotConfig; + if ( entity instanceof EntityPlayer) + { + return config.playerTrackingRange; + } else if ( entity.activationType == 1 ) + { + return config.monsterTrackingRange; + } else if ( entity instanceof EntityGhast) + { + if ( config.monsterTrackingRange > config.monsterActivationRange ) + { + return config.monsterTrackingRange; + } else + { + return config.monsterActivationRange; + } + } else if ( entity.activationType == 2 ) + { + return config.animalTrackingRange; + } else if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityXPOrb) + { + return config.miscTrackingRange; + } else + { + return config.otherTrackingRange; + } + } +} diff --git a/src/main/java/org/spigotmc/ValidateUtils.java b/src/main/java/org/spigotmc/ValidateUtils.java new file mode 100644 index 00000000..58a95348 --- /dev/null +++ b/src/main/java/org/spigotmc/ValidateUtils.java @@ -0,0 +1,14 @@ +package org.spigotmc; + +public class ValidateUtils +{ + + public static String limit(String str, int limit) + { + if ( str.length() > limit ) + { + return str.substring( 0, limit ); + } + return str; + } +} diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java new file mode 100644 index 00000000..eda1e5f1 --- /dev/null +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -0,0 +1,125 @@ +package org.spigotmc; + +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import org.bukkit.Bukkit; + +public class WatchdogThread extends Thread +{ + + private static WatchdogThread instance; + private final long timeoutTime; + private final boolean restart; + private volatile long lastTick; + private volatile boolean stopping; + + private WatchdogThread(long timeoutTime, boolean restart) + { + super( "Spigot Watchdog Thread" ); + this.timeoutTime = timeoutTime; + this.restart = restart; + } + + public static void doStart(int timeoutTime, boolean restart) + { + if ( instance == null ) + { + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } + } + + public static void tick() + { + instance.lastTick = System.currentTimeMillis(); + } + + public static void doStop() + { + if ( instance != null ) + { + instance.stopping = true; + } + } + + @Override + public void run() + { + while ( !stopping ) + { + // + if ( lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime ) + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "The server has stopped responding!" ); + log.log( Level.SEVERE, "Please report this to http://www.spigotmc.org/" ); + log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); + log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); + // +// if(World.haveWeSilencedAPhysicsCrash) +// { +// log.log( Level.SEVERE, "------------------------------" ); +// log.log( Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed" ); +// log.log( Level.SEVERE, "near " + World.blockLocation); +// } + // + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServerInstance().primaryThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + dumpThread( thread, log ); + } + log.log( Level.SEVERE, "------------------------------" ); + + if ( restart ) + { + MinecraftServer.getServerInstance().primaryThread.stop(); + } + break; + } + + try + { + sleep( 10000 ); + } catch ( InterruptedException ex ) + { + interrupt(); + } + } + } + + private static void dumpThread(ThreadInfo thread, Logger log) + { + log.log( Level.SEVERE, "------------------------------" ); + // + log.log( Level.SEVERE, "Current Thread: " + thread.getThreadName() ); + log.log( Level.SEVERE, "\tPID: " + thread.getThreadId() + + " | Suspended: " + thread.isSuspended() + + " | Native: " + thread.isInNative() + + " | State: " + thread.getThreadState() ); + if ( thread.getLockedMonitors().length != 0 ) + { + log.log( Level.SEVERE, "\tThread is waiting on monitor(s):" ); + for ( MonitorInfo monitor : thread.getLockedMonitors() ) + { + log.log( Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame() ); + } + } + log.log( Level.SEVERE, "\tStack:" ); + // + for ( StackTraceElement stack : thread.getStackTrace() ) + { + log.log( Level.SEVERE, "\t\t" + stack ); + } + } +} diff --git a/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java b/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java new file mode 100644 index 00000000..24d4942a --- /dev/null +++ b/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java @@ -0,0 +1,39 @@ +package org.spigotmc.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Called when an entity stops riding another entity. + * + */ +public class EntityDismountEvent extends EntityEvent +{ + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity dismounted; + + public EntityDismountEvent(Entity what, Entity dismounted) + { + super( what ); + this.dismounted = dismounted; + } + + public Entity getDismounted() + { + return dismounted; + } + + @Override + public HandlerList getHandlers() + { + return handlers; + } + + public static HandlerList getHandlerList() + { + return handlers; + } +} diff --git a/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java b/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java new file mode 100644 index 00000000..16aa2a7e --- /dev/null +++ b/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java @@ -0,0 +1,52 @@ +package org.spigotmc.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +/** + * Called when an entity attempts to ride another entity. + * + */ +public class EntityMountEvent extends EntityEvent implements Cancellable +{ + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity mount; + + public EntityMountEvent(Entity what, Entity mount) + { + super( what ); + this.mount = mount; + } + + public Entity getMount() + { + return mount; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) + { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() + { + return handlers; + } + + public static HandlerList getHandlerList() + { + return handlers; + } +} diff --git a/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java b/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java new file mode 100644 index 00000000..783d696f --- /dev/null +++ b/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java @@ -0,0 +1,49 @@ +package org.spigotmc.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when player is about to spawn in a world after joining the server. + */ +public class PlayerSpawnLocationEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private Location spawnLocation; + + public PlayerSpawnLocationEvent(final Player who, Location spawnLocation) { + super(who); + this.spawnLocation = spawnLocation; + } + + + /** + * Gets player's spawn location. + * If the player {@link Player#hasPlayedBefore()}, it's going to default to the location inside player.dat file. + * For new players, the default spawn location is spawn of the main Bukkit world. + * + * @return the spawn location + */ + public Location getSpawnLocation() { + return spawnLocation; + } + + /** + * Sets player's spawn location. + * + * @param location the spawn location + */ + public void setSpawnLocation(Location location) { + this.spawnLocation = location; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml new file mode 100644 index 00000000..67ea88a4 --- /dev/null +++ b/src/main/resources/configurations/bukkit.yml @@ -0,0 +1,37 @@ +# This is the main configuration file for Bukkit. +# As you can see, there's actually not that much to configure without any plugins. +# For a reference for any variable inside this file, check out the Bukkit Wiki at +# http://wiki.bukkit.org/Bukkit.yml +# +# If you need help on this file, feel free to join us on irc or leave a message +# on the forums asking for advice. +# +# IRC: #spigot @ irc.spi.gt +# (If this means nothing to you, just go to http://www.spigotmc.org/pages/irc/ ) +# Forums: http://www.spigotmc.org/ +# Bug tracker: http://www.spigotmc.org/go/bugs + + +settings: + allow-end: true + warn-on-overload: true + permissions-file: permissions.yml + update-folder: update + plugin-profiling: false + connection-throttle: 4000 + query-plugins: true + deprecated-verbose: default + shutdown-message: Server closed +spawn-limits: + monsters: 70 + animals: 15 + water-animals: 5 + ambient: 15 +chunk-gc: + period-in-ticks: 600 + load-threshold: 0 +ticks-per: + animal-spawns: 400 + monster-spawns: 1 + autosave: 6000 +aliases: now-in-commands.yml diff --git a/src/main/resources/configurations/commands.yml b/src/main/resources/configurations/commands.yml new file mode 100644 index 00000000..0491a8bb --- /dev/null +++ b/src/main/resources/configurations/commands.yml @@ -0,0 +1,17 @@ +# This is the commands configuration file for Bukkit. +# For documentation on how to make use of this file, check out the Bukkit Wiki at +# http://wiki.bukkit.org/Commands.yml +# +# If you need help on this file, feel free to join us on irc or leave a message +# on the forums asking for advice. +# +# IRC: #spigot @ irc.spi.gt +# (If this means nothing to you, just go to http://www.spigotmc.org/pages/irc/ ) +# Forums: http://www.spigotmc.org/ +# Bug tracker: http://www.spigotmc.org/go/bugs + +command-block-overrides: [] +unrestricted-advancements: false +aliases: + icanhasbukkit: + - "version $1-" diff --git a/src/main/resources/configurations/help.yml b/src/main/resources/configurations/help.yml new file mode 100644 index 00000000..15c3d070 --- /dev/null +++ b/src/main/resources/configurations/help.yml @@ -0,0 +1,55 @@ +# This is the help configuration file for Bukkit. +# +# By default you do not need to modify this file. Help topics for all plugin commands are automatically provided by +# or extracted from your installed plugins. You only need to modify this file if you wish to add new help pages to +# your server or override the help pages of existing plugin commands. +# +# This file is divided up into the following parts: +# -- general-topics: lists admin defined help topics +# -- index-topics: lists admin defined index topics +# -- amend-topics: lists topic amendments to apply to existing help topics +# -- ignore-plugins: lists any plugins that should be excluded from help +# +# Examples are given below. When amending command topic, the string will be replaced with the existing value +# in the help topic. Color codes can be used in topic text. The color code character is & followed by 0-F. +# ================================================================ +# +# Set this to true to list the individual command help topics in the master help. +# command-topics-in-master-index: true +# +# Each general topic will show up as a separate topic in the help index along with all the plugin command topics. +# general-topics: +# Rules: +# shortText: Rules of the server +# fullText: | +# &61. Be kind to your fellow players. +# &B2. No griefing. +# &D3. No swearing. +# permission: topics.rules +# +# Each index topic will show up as a separate sub-index in the help index along with all the plugin command topics. +# To override the default help index (displayed when the user executes /help), name the index topic "Default". +# index-topics: +# Ban Commands: +# shortText: Player banning commands +# preamble: Moderator - do not abuse these commands +# permission: op +# commands: +# - /ban +# - /ban-ip +# - /banlist +# +# Topic amendments are used to change the content of automatically generated plugin command topics. +# amended-topics: +# /stop: +# shortText: Stops the server cold....in its tracks! +# fullText: - This kills the server. +# permission: you.dont.have +# +# Any plugin in the ignored plugins list will be excluded from help. The name must match the name displayed by +# the /plugins command. Ignore "Bukkit" to remove the standard bukkit commands from the index. Ignore "All" +# to completely disable automatic help topic generation. +# ignore-plugins: +# - PluginNameOne +# - PluginNameTwo +# - PluginNameThree